Issue #13477: Added command line interface to the tarfile module.

Original patch by Berker Peksag.
This commit is contained in:
Serhiy Storchaka 2013-11-24 01:53:29 +02:00
parent 44e2eaab54
commit d27b455bbc
4 changed files with 323 additions and 1 deletions

View File

@ -591,6 +591,67 @@ A :class:`TarInfo` object also provides some convenient query methods:
Return :const:`True` if it is one of character device, block device or FIFO.
.. _tarfile-commandline:
Command Line Interface
----------------------
.. versionadded:: 3.4
The :mod:`tarfile` module provides a simple command line interface to interact
with tar archives.
If you want to create a new tar archive, specify its name after the :option:`-c`
option and then list the filename(s) that should be included::
$ python -m tarfile -c monty.tar spam.txt eggs.txt
Passing a directory is also acceptable::
$ python -m tarfile -c monty.tar life-of-brian_1979/
If you want to extract a tar archive into the current directory, use
the :option:`-e` option::
$ python -m tarfile -e monty.tar
You can also extract a tar archive into a different directory by passing the
directory's name::
$ python -m tarfile -e monty.tar other-dir/
For a list of the files in a tar archive, use the :option:`-l` option::
$ python -m tarfile -l monty.tar
Command line options
~~~~~~~~~~~~~~~~~~~~
.. cmdoption:: -l <tarfile>
--list <tarfile>
List files in a tarfile.
.. cmdoption:: -c <tarfile> <source1> <sourceN>
--create <tarfile> <source1> <sourceN>
Create tarfile from source files.
.. cmdoption:: -e <tarfile> [<output_dir>]
--extract <tarfile> [<output_dir>]
Extract tarfile into the current directory if *output_dir* is not specified.
.. cmdoption:: -t <tarfile>
--test <tarfile>
Test whether the tarfile is valid or not.
.. cmdoption:: -v, --verbose
Verbose output
.. _tar-examples:
Examples

View File

@ -2404,3 +2404,97 @@ def is_tarfile(name):
bltn_open = open
open = TarFile.open
def main():
import argparse
description = 'A simple command line interface for tarfile module.'
parser = argparse.ArgumentParser(description=description)
parser.add_argument('-v', '--verbose', action='store_true', default=False,
help='Verbose output')
group = parser.add_mutually_exclusive_group()
group.add_argument('-l', '--list', metavar='<tarfile>',
help='Show listing of a tarfile')
group.add_argument('-e', '--extract', nargs='+',
metavar=('<tarfile>', '<output_dir>'),
help='Extract tarfile into target dir')
group.add_argument('-c', '--create', nargs='+',
metavar=('<name>', '<file>'),
help='Create tarfile from sources')
group.add_argument('-t', '--test', metavar='<tarfile>',
help='Test if a tarfile is valid')
args = parser.parse_args()
if args.test:
src = args.test
if is_tarfile(src):
with open(src, 'r') as tar:
tar.getmembers()
print(tar.getmembers(), file=sys.stderr)
if args.verbose:
print('{!r} is a tar archive.'.format(src))
else:
parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
elif args.list:
src = args.list
if is_tarfile(src):
with TarFile.open(src, 'r:*') as tf:
tf.list(verbose=args.verbose)
else:
parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
elif args.extract:
if len(args.extract) == 1:
src = args.extract[0]
curdir = os.curdir
elif len(args.extract) == 2:
src, curdir = args.extract
else:
parser.exit(1, parser.format_help())
if is_tarfile(src):
with TarFile.open(src, 'r:*') as tf:
tf.extractall(path=curdir)
if args.verbose:
if curdir == '.':
msg = '{!r} file is extracted.'.format(src)
else:
msg = ('{!r} file is extracted '
'into {!r} directory.').format(src, curdir)
print(msg)
else:
parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
elif args.create:
tar_name = args.create.pop(0)
_, ext = os.path.splitext(tar_name)
compressions = {
# gz
'gz': 'gz',
'tgz': 'gz',
# xz
'xz': 'xz',
'txz': 'xz',
# bz2
'bz2': 'bz2',
'tbz': 'bz2',
'tbz2': 'bz2',
'tb2': 'bz2',
}
tar_mode = 'w:' + compressions[ext] if ext in compressions else 'w'
tar_files = args.create
with TarFile.open(tar_name, tar_mode) as tf:
for file_name in tar_files:
tf.add(file_name)
if args.verbose:
print('{!r} file created.'.format(tar_name))
else:
parser.exit(1, parser.format_help())
if __name__ == '__main__':
main()

View File

@ -7,7 +7,7 @@ from hashlib import md5
import unittest
import tarfile
from test import support
from test import support, script_helper
# Check for our compression modules.
try:
@ -27,11 +27,13 @@ def md5sum(data):
return md5(data).hexdigest()
TEMPDIR = os.path.abspath(support.TESTFN) + "-tardir"
tarextdir = TEMPDIR + '-extract-test'
tarname = support.findfile("testtar.tar")
gzipname = os.path.join(TEMPDIR, "testtar.tar.gz")
bz2name = os.path.join(TEMPDIR, "testtar.tar.bz2")
xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
tmpname = os.path.join(TEMPDIR, "tmp.tar")
dotlessname = os.path.join(TEMPDIR, "testtar")
md5_regtype = "65f477c818ad9e15f7feab0c6d37742f"
md5_sparse = "a54fbc4ca4f4399a90e1b27164012fc6"
@ -1724,6 +1726,168 @@ class MiscTest(unittest.TestCase):
tarfile.itn(0x10000000000, 6, tarfile.GNU_FORMAT)
class CommandLineTest(unittest.TestCase):
def tarfilecmd(self, *args):
rc, out, err = script_helper.assert_python_ok('-m', 'tarfile', *args)
return out
def tarfilecmd_failure(self, *args):
return script_helper.assert_python_failure('-m', 'tarfile', *args)
def make_simple_tarfile(self, tar_name):
files = [support.findfile('tokenize_tests.txt'),
support.findfile('tokenize_tests-no-coding-cookie-'
'and-utf8-bom-sig-only.txt')]
self.addCleanup(support.unlink, tar_name)
with tarfile.open(tar_name, 'w') as tf:
for tardata in files:
tf.add(tardata, arcname=os.path.basename(tardata))
def test_test_command(self):
for tar_name in (tarname, gzipname, bz2name, xzname):
for opt in '-t', '--test':
out = self.tarfilecmd(opt, tar_name)
self.assertEqual(out, b'')
def test_test_command_verbose(self):
for tar_name in (tarname, gzipname, bz2name, xzname):
for opt in '-v', '--verbose':
out = self.tarfilecmd(opt, '-t', tar_name)
self.assertIn(b'is a tar archive.\n', out)
def test_test_command_invalid_file(self):
zipname = support.findfile('zipdir.zip')
rc, out, err = self.tarfilecmd_failure('-t', zipname)
self.assertIn(b' is not a tar archive.', err)
self.assertEqual(out, b'')
self.assertEqual(rc, 1)
for tar_name in (tarname, gzipname, bz2name, xzname):
with self.subTest(tar_name=tar_name):
with open(tar_name, 'rb') as f:
data = f.read()
try:
with open(tmpname, 'wb') as f:
f.write(data[:511])
rc, out, err = self.tarfilecmd_failure('-t', tmpname)
self.assertEqual(out, b'')
self.assertEqual(rc, 1)
finally:
support.unlink(tmpname)
def test_list_command(self):
self.make_simple_tarfile(tmpname)
with support.captured_stdout() as t:
with tarfile.open(tmpname, 'r') as tf:
tf.list(verbose=False)
expected = t.getvalue().encode(sys.getfilesystemencoding())
for opt in '-l', '--list':
out = self.tarfilecmd(opt, tmpname)
self.assertEqual(out, expected)
def test_list_command_verbose(self):
self.make_simple_tarfile(tmpname)
with support.captured_stdout() as t:
with tarfile.open(tmpname, 'r') as tf:
tf.list(verbose=True)
expected = t.getvalue().encode(sys.getfilesystemencoding())
for opt in '-v', '--verbose':
out = self.tarfilecmd(opt, '-l', tmpname)
self.assertEqual(out, expected)
def test_list_command_invalid_file(self):
zipname = support.findfile('zipdir.zip')
rc, out, err = self.tarfilecmd_failure('-l', zipname)
self.assertIn(b' is not a tar archive.', err)
self.assertEqual(out, b'')
self.assertEqual(rc, 1)
def test_create_command(self):
files = [support.findfile('tokenize_tests.txt'),
support.findfile('tokenize_tests-no-coding-cookie-'
'and-utf8-bom-sig-only.txt')]
for opt in '-c', '--create':
try:
out = self.tarfilecmd(opt, tmpname, *files)
self.assertEqual(out, b'')
with tarfile.open(tmpname) as tar:
tar.getmembers()
finally:
support.unlink(tmpname)
def test_create_command_verbose(self):
files = [support.findfile('tokenize_tests.txt'),
support.findfile('tokenize_tests-no-coding-cookie-'
'and-utf8-bom-sig-only.txt')]
for opt in '-v', '--verbose':
try:
out = self.tarfilecmd(opt, '-c', tmpname, *files)
self.assertIn(b' file created.', out)
with tarfile.open(tmpname) as tar:
tar.getmembers()
finally:
support.unlink(tmpname)
def test_create_command_dotless_filename(self):
files = [support.findfile('tokenize_tests.txt')]
try:
out = self.tarfilecmd('-c', dotlessname, *files)
self.assertEqual(out, b'')
with tarfile.open(dotlessname) as tar:
tar.getmembers()
finally:
support.unlink(dotlessname)
def test_create_command_dot_started_filename(self):
tar_name = os.path.join(TEMPDIR, ".testtar")
files = [support.findfile('tokenize_tests.txt')]
try:
out = self.tarfilecmd('-c', tar_name, *files)
self.assertEqual(out, b'')
with tarfile.open(tar_name) as tar:
tar.getmembers()
finally:
support.unlink(tar_name)
def test_extract_command(self):
self.make_simple_tarfile(tmpname)
for opt in '-e', '--extract':
try:
with support.temp_cwd(tarextdir):
out = self.tarfilecmd(opt, tmpname)
self.assertEqual(out, b'')
finally:
support.rmtree(tarextdir)
def test_extract_command_verbose(self):
self.make_simple_tarfile(tmpname)
for opt in '-v', '--verbose':
try:
with support.temp_cwd(tarextdir):
out = self.tarfilecmd(opt, '-e', tmpname)
self.assertIn(b' file is extracted.', out)
finally:
support.rmtree(tarextdir)
def test_extract_command_different_directory(self):
self.make_simple_tarfile(tmpname)
try:
with support.temp_cwd(tarextdir):
out = self.tarfilecmd('-e', tmpname, 'spamdir')
self.assertEqual(out, b'')
finally:
support.rmtree(tarextdir)
def test_extract_command_invalid_file(self):
zipname = support.findfile('zipdir.zip')
with support.temp_cwd(tarextdir):
rc, out, err = self.tarfilecmd_failure('-e', zipname)
self.assertIn(b' is not a tar archive.', err)
self.assertEqual(out, b'')
self.assertEqual(rc, 1)
class ContextManagerTest(unittest.TestCase):
def test_basic(self):

View File

@ -68,6 +68,9 @@ Core and Builtins
Library
-------
- Issue #13477: Added command line interface to the tarfile module.
Original patch by Berker Peksag.
- Issue #19674: inspect.signature() now produces a correct signature
for some builtins.