dtrx-7.1/0000700000175000017500000000000011662032736011360 5ustar brettbrettdtrx-7.1/scripts/0000700000175000017500000000000011662032736013047 5ustar brettbrettdtrx-7.1/scripts/dtrx0000700000175000017500000014431711662032473013766 0ustar brettbrett#!/usr/bin/env python # -*- coding: utf-8 -*- # # dtrx -- Intelligently extract various archive types. # Copyright © 2006-2011 Brett Smith # Copyright © 2008 Peter Kelemen # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, see . # Python 2.3 string methods: 'rfind', 'rindex', 'rjust', 'rstrip' import errno import fcntl import logging import mimetypes import optparse import os import re import shutil import signal import stat import string import struct import subprocess import sys import tempfile import termios import textwrap import traceback import urlparse try: set except NameError: from sets import Set as set VERSION = "7.1" VERSION_BANNER = """dtrx version %s Copyright © 2006-2011 Brett Smith Copyright © 2008 Peter Kelemen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.""" % (VERSION,) MATCHING_DIRECTORY = 1 ONE_ENTRY_KNOWN = 2 BOMB = 3 EMPTY = 4 ONE_ENTRY_FILE = 'file' ONE_ENTRY_DIRECTORY = 'directory' ONE_ENTRY_UNKNOWN = [ONE_ENTRY_FILE, ONE_ENTRY_DIRECTORY] EXTRACT_HERE = 1 EXTRACT_WRAP = 2 EXTRACT_RENAME = 3 RECURSE_ALWAYS = 1 RECURSE_ONCE = 2 RECURSE_NOT_NOW = 3 RECURSE_NEVER = 4 RECURSE_LIST = 5 mimetypes.encodings_map.setdefault('.bz2', 'bzip2') mimetypes.encodings_map.setdefault('.lzma', 'lzma') mimetypes.encodings_map.setdefault('.xz', 'xz') mimetypes.encodings_map.setdefault('.lz', 'lzip') mimetypes.types_map.setdefault('.gem', 'application/x-ruby-gem') logger = logging.getLogger('dtrx-log') class FilenameChecker(object): free_func = os.open free_args = (os.O_CREAT | os.O_EXCL,) free_close = os.close def __init__(self, original_name): self.original_name = original_name def is_free(self, filename): try: result = self.free_func(filename, *self.free_args) except OSError, error: if error.errno == errno.EEXIST: return False raise if self.free_close: self.free_close(result) return True def create(self): fd, filename = tempfile.mkstemp(prefix=self.original_name + '.', dir='.') os.close(fd) return filename def check(self): for suffix in [''] + ['.%s' % (x,) for x in range(1, 10)]: filename = '%s%s' % (self.original_name, suffix) if self.is_free(filename): return filename return self.create() class DirectoryChecker(FilenameChecker): free_func = os.mkdir free_args = () free_close = None def create(self): return tempfile.mkdtemp(prefix=self.original_name + '.', dir='.') class ExtractorError(Exception): pass class ExtractorUnusable(Exception): pass EXTRACTION_ERRORS = (ExtractorError, ExtractorUnusable, OSError, IOError) class BaseExtractor(object): decoders = {'bzip2': ['bzcat'], 'gzip': ['zcat'], 'compress': ['zcat'], 'lzma': ['lzcat'], 'xz': ['xzcat'], 'lzip': ['lzip', '-cd']} name_checker = DirectoryChecker def __init__(self, filename, encoding): if encoding and (not self.decoders.has_key(encoding)): raise ValueError("unrecognized encoding %s" % (encoding,)) self.filename = os.path.realpath(filename) self.encoding = encoding self.file_count = 0 self.included_archives = [] self.target = None self.content_type = None self.content_name = None self.pipes = [] self.stderr = tempfile.TemporaryFile() self.exit_codes = [] try: self.archive = open(filename, 'r') except (IOError, OSError), error: raise ExtractorError("could not open %s: %s" % (filename, error.strerror)) if encoding: self.pipe(self.decoders[encoding], "decoding") self.prepare() def pipe(self, command, description="extraction"): self.pipes.append((command, description)) def add_process(self, processes, command, stdin, stdout): try: processes.append(subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=self.stderr)) except OSError, error: if error.errno == errno.ENOENT: raise ExtractorUnusable("could not run %s" % (command[0],)) raise def run_pipes(self, final_stdout=None): if not self.pipes: return elif final_stdout is None: final_stdout = open('/dev/null', 'w') num_pipes = len(self.pipes) last_pipe = num_pipes - 1 processes = [] for index, command in enumerate([pipe[0] for pipe in self.pipes]): if index == 0: stdin = self.archive else: stdin = processes[-1].stdout if index == last_pipe: stdout = final_stdout else: stdout = subprocess.PIPE self.add_process(processes, command, stdin, stdout) self.exit_codes = [pipe.wait() for pipe in processes] self.archive.close() for index in range(last_pipe): processes[index].stdout.close() self.archive = final_stdout def prepare(self): pass def check_included_archives(self): if (self.content_name is None) or (not self.content_name.endswith('/')): self.included_root = './' else: self.included_root = self.content_name start_index = len(self.included_root) for path, dirname, filenames in os.walk(self.included_root): self.file_count += len(filenames) path = path[start_index:] for filename in filenames: if (ExtractorBuilder.try_by_mimetype(filename) or ExtractorBuilder.try_by_extension(filename)): self.included_archives.append(os.path.join(path, filename)) def check_contents(self): if not self.contents: self.content_type = EMPTY elif len(self.contents) == 1: if self.basename() == self.contents[0]: self.content_type = MATCHING_DIRECTORY elif os.path.isdir(self.contents[0]): self.content_type = ONE_ENTRY_DIRECTORY else: self.content_type = ONE_ENTRY_FILE self.content_name = self.contents[0] if os.path.isdir(self.contents[0]): self.content_name += '/' else: self.content_type = BOMB self.check_included_archives() def basename(self): pieces = os.path.basename(self.filename).split('.') orig_len = len(pieces) extension = '.' + pieces[-1] # This is maybe a little more clever than it ought to be. # We're trying to be conservative about what remove, but also DTRT # in cases like .tar.gz, and also do something reasonable if we # encounter some completely off-the-wall extension. So that means: # 1. First remove any compression extension. # 2. Then remove any commonly known extension that remains. # 3. If neither of those did anything, remove anything that looks # like it's almost certainly an extension (less than 5 chars). if mimetypes.encodings_map.has_key(extension): pieces.pop() extension = '.' + pieces[-1] if (mimetypes.types_map.has_key(extension) or mimetypes.common_types.has_key(extension) or mimetypes.suffix_map.has_key(extension)): pieces.pop() if ((orig_len == len(pieces)) and (orig_len > 1) and (len(pieces[-1]) < 5)): pieces.pop() return '.'.join(pieces) def get_stderr(self): self.stderr.seek(0, 0) errors = self.stderr.read(-1) self.stderr.close() return errors def is_fatal_error(self, status): return False def first_bad_exit_code(self): for index, code in enumerate(self.exit_codes): if code > 0: return index, code return None, None def check_success(self, got_files): error_index, error_code = self.first_bad_exit_code() logger.debug("success results: %s %s %s" % (got_files, error_index, self.exit_codes)) if (self.is_fatal_error(error_code) or ((not got_files) and (error_code is not None))): command = ' '.join(self.pipes[error_index][0]) raise ExtractorError("%s error: '%s' returned status code %s" % (self.pipes[error_index][1], command, error_code)) def extract_archive(self): self.pipe(self.extract_pipe) self.run_pipes() def extract(self): try: self.target = tempfile.mkdtemp(prefix='.dtrx-', dir='.') except (OSError, IOError), error: raise ExtractorError("cannot extract here: %s" % (error.strerror,)) old_path = os.path.realpath(os.curdir) os.chdir(self.target) try: self.archive.seek(0, 0) self.extract_archive() self.contents = os.listdir('.') self.check_contents() self.check_success(self.content_type != EMPTY) except EXTRACTION_ERRORS: self.archive.close() os.chdir(old_path) shutil.rmtree(self.target, ignore_errors=True) raise self.archive.close() os.chdir(old_path) def get_filenames(self, internal=False): if not internal: self.pipe(self.list_pipe, "listing") processes = [] stdin = self.archive for command in [pipe[0] for pipe in self.pipes]: self.add_process(processes, command, stdin, subprocess.PIPE) stdin = processes[-1].stdout get_output_line = processes[-1].stdout.readline while True: line = get_output_line() if not line: break yield line.rstrip('\n') self.exit_codes = [pipe.wait() for pipe in processes] self.archive.close() for process in processes: process.stdout.close() self.check_success(False) class CompressionExtractor(BaseExtractor): file_type = 'compressed file' name_checker = FilenameChecker def basename(self): pieces = os.path.basename(self.filename).split('.') extension = '.' + pieces[-1] if mimetypes.encodings_map.has_key(extension): pieces.pop() return '.'.join(pieces) def get_filenames(self): # This code used to just immediately yield the basename, under the # assumption that that would be the filename. However, if that # happens, dtrx -l will report this as a valid result for files with # compression extensions, even if those files shouldn't actually be # handled this way. So, we call out to the file command to do a quick # check and make sure this actually looks like a compressed file. if 'compress' not in [match[0] for match in ExtractorBuilder.try_by_magic(self.filename)]: raise ExtractorError("doesn't look like a compressed file") yield self.basename() def extract(self): self.content_type = ONE_ENTRY_KNOWN self.content_name = self.basename() self.contents = None self.file_count = 1 self.included_root = './' try: output_fd, self.target = tempfile.mkstemp(prefix='.dtrx-', dir='.') except (OSError, IOError), error: raise ExtractorError("cannot extract here: %s" % (error.strerror,)) self.run_pipes(output_fd) os.close(output_fd) try: self.check_success(os.stat(self.target)[stat.ST_SIZE] > 0) except EXTRACTION_ERRORS: os.unlink(self.target) raise class TarExtractor(BaseExtractor): file_type = 'tar file' extract_pipe = ['tar', '-x'] list_pipe = ['tar', '-t'] class CpioExtractor(BaseExtractor): file_type = 'cpio file' extract_pipe = ['cpio', '-i', '--make-directories', '--quiet', '--no-absolute-filenames'] list_pipe = ['cpio', '-t', '--quiet'] class RPMExtractor(CpioExtractor): file_type = 'RPM' def prepare(self): self.pipe(['rpm2cpio', '-'], "rpm2cpio") def basename(self): pieces = os.path.basename(self.filename).split('.') if len(pieces) == 1: return pieces[0] elif pieces[-1] != 'rpm': return BaseExtractor.basename(self) pieces.pop() if len(pieces) == 1: return pieces[0] elif len(pieces[-1]) < 8: pieces.pop() return '.'.join(pieces) def check_contents(self): self.check_included_archives() self.content_type = BOMB class DebExtractor(TarExtractor): file_type = 'Debian package' data_re = re.compile(r'^data\.tar\.[a-z0-9]+$') def prepare(self): self.pipe(['ar', 't', self.filename], "finding package data file") for filename in self.get_filenames(internal=True): if self.data_re.match(filename): data_filename = filename break else: raise ExtractorError(".deb contains no data.tar file") self.archive.seek(0, 0) self.pipes.pop() # self.pipes = start_pipes encoding = mimetypes.guess_type(data_filename)[1] if not encoding: raise ExtractorError("data.tar file has unrecognized encoding") self.pipe(['ar', 'p', self.filename, data_filename], "extracting data.tar from .deb") self.pipe(self.decoders[encoding], "decoding data.tar") def basename(self): pieces = os.path.basename(self.filename).split('_') if len(pieces) == 1: return pieces[0] last_piece = pieces.pop() if (len(last_piece) > 10) or (not last_piece.endswith('.deb')): return BaseExtractor.basename(self) return '_'.join(pieces) def check_contents(self): self.check_included_archives() self.content_type = BOMB class DebMetadataExtractor(DebExtractor): def prepare(self): self.pipe(['ar', 'p', self.filename, 'control.tar.gz'], "control.tar.gz extraction") self.pipe(['zcat'], "control.tar.gz decompression") class GemExtractor(TarExtractor): file_type = 'Ruby gem' def prepare(self): self.pipe(['tar', '-xO', 'data.tar.gz'], "data.tar.gz extraction") self.pipe(['zcat'], "data.tar.gz decompression") def check_contents(self): self.check_included_archives() self.content_type = BOMB class GemMetadataExtractor(CompressionExtractor): file_type = 'Ruby gem' def prepare(self): self.pipe(['tar', '-xO', 'metadata.gz'], "metadata.gz extraction") self.pipe(['zcat'], "metadata.gz decompression") def basename(self): return os.path.basename(self.filename) + '-metadata.txt' class NoPipeExtractor(BaseExtractor): # Some extraction tools won't accept the archive from stdin. With # these, the piping infrastructure we normally set up generally doesn't # work, at least at first. We can still use most of it; we just don't # want to seed self.archive with the archive file, since that sucks up # memory. So instead we seed it with /dev/null, and specify the # filename on the command line as necessary. We also open the actual # file with os.open, to make sure we can actually do it (permissions # are good, etc.). This class doesn't do anything by itself; it's just # meant to be a base class for extractors that rely on these dumb # tools. def __init__(self, filename, encoding): os.close(os.open(filename, os.O_RDONLY)) BaseExtractor.__init__(self, '/dev/null', None) self.filename = os.path.realpath(filename) def extract_archive(self): self.extract_pipe = self.extract_command + [self.filename] BaseExtractor.extract_archive(self) def get_filenames(self): self.list_pipe = self.list_command + [self.filename] return BaseExtractor.get_filenames(self) class ZipExtractor(NoPipeExtractor): file_type = 'Zip file' extract_command = ['unzip', '-q'] list_command = ['zipinfo', '-1'] def is_fatal_error(self, status): return status > 1 class LZHExtractor(ZipExtractor): file_type = 'LZH file' extract_command = ['lha', 'xq'] list_command = ['lha', 'l'] def border_line_file_index(self, line): last_space_index = None for index, char in enumerate(line): if char == ' ': last_space_index = index elif char != '-': return None if last_space_index is None: return None return last_space_index + 1 def get_filenames(self): filenames = NoPipeExtractor.get_filenames(self) for line in filenames: fn_index = self.border_line_file_index(line) if fn_index is not None: break for line in filenames: if self.border_line_file_index(line): break else: yield line[fn_index:] self.archive.close() class SevenExtractor(NoPipeExtractor): file_type = '7z file' extract_command = ['7z', 'x'] list_command = ['7z', 'l'] border_re = re.compile('^[- ]+$') def get_filenames(self): fn_index = None for line in NoPipeExtractor.get_filenames(self): if self.border_re.match(line): if fn_index is not None: break else: fn_index = string.rindex(line, ' ') + 1 elif fn_index is not None: yield line[fn_index:] self.archive.close() class CABExtractor(NoPipeExtractor): file_type = 'CAB archive' extract_command = ['cabextract', '-q'] list_command = ['cabextract', '-l'] border_re = re.compile(r'^[-\+]+$') def get_filenames(self): fn_index = None filenames = NoPipeExtractor.get_filenames(self) for line in filenames: if self.border_re.match(line): break for line in filenames: try: yield line.split(' | ', 2)[2] except IndexError: break self.archive.close() class ShieldExtractor(NoPipeExtractor): file_type = 'InstallShield archive' extract_command = ['unshield', 'x'] list_command = ['unshield', 'l'] prefix_re = re.compile(r'^\s+\d+\s+') end_re = re.compile(r'^\s+-+\s+-+\s*$') def get_filenames(self): for line in NoPipeExtractor.get_filenames(self): if self.end_re.match(line): break else: match = self.prefix_re.match(line) if match: yield line[match.end():] self.archive.close() def basename(self): result = NoPipeExtractor.basename(self) if result.endswith('.hdr'): result = result[:-4] return result class RarExtractor(NoPipeExtractor): file_type = 'RAR archive' extract_command = ['unrar', 'x'] list_command = ['unrar', 'l'] border_re = re.compile('^-+$') def get_filenames(self): inside = False for line in NoPipeExtractor.get_filenames(self): if self.border_re.match(line): if inside: break else: inside = True elif inside: yield line.split(' ')[1] self.archive.close() class BaseHandler(object): def __init__(self, extractor, options): self.extractor = extractor self.options = options self.target = None def handle(self): command = 'find' status = subprocess.call(['find', self.extractor.target, '-type', 'd', '-exec', 'chmod', 'u+rwx', '{}', ';']) if status == 0: command = 'chmod' status = subprocess.call(['chmod', '-R', 'u+rwX', self.extractor.target]) if status != 0: return "%s returned with exit status %s" % (command, status) return self.organize() def set_target(self, target, checker): self.target = checker(target).check() if self.target != target: logger.warning("extracting %s to %s" % (self.extractor.filename, self.target)) # The "where to extract" table, with options and archive types. # This dictates the contents of each can_handle method. # # Flat Overwrite None # File basename basename FilenameChecked # Match . . tempdir + checked # Bomb . basename DirectoryChecked class FlatHandler(BaseHandler): def can_handle(contents, options): return ((options.flat and (contents != ONE_ENTRY_KNOWN)) or (options.overwrite and (contents == MATCHING_DIRECTORY))) can_handle = staticmethod(can_handle) def organize(self): self.target = '.' for curdir, dirs, filenames in os.walk(self.extractor.target, topdown=False): path_parts = curdir.split(os.sep) if path_parts[0] == '.': del path_parts[1] else: del path_parts[0] newdir = os.path.join(*path_parts) if not os.path.isdir(newdir): os.makedirs(newdir) for filename in filenames: os.rename(os.path.join(curdir, filename), os.path.join(newdir, filename)) os.rmdir(curdir) class OverwriteHandler(BaseHandler): def can_handle(contents, options): return ((options.flat and (contents == ONE_ENTRY_KNOWN)) or (options.overwrite and (contents != MATCHING_DIRECTORY))) can_handle = staticmethod(can_handle) def organize(self): self.target = self.extractor.basename() if os.path.isdir(self.target): shutil.rmtree(self.target) os.rename(self.extractor.target, self.target) class MatchHandler(BaseHandler): def can_handle(contents, options): return ((contents == MATCHING_DIRECTORY) or ((contents in ONE_ENTRY_UNKNOWN) and options.one_entry_policy.ok_for_match())) can_handle = staticmethod(can_handle) def organize(self): source = os.path.join(self.extractor.target, os.listdir(self.extractor.target)[0]) if os.path.isdir(source): checker = DirectoryChecker else: checker = FilenameChecker if self.options.one_entry_policy == EXTRACT_HERE: destination = self.extractor.content_name.rstrip('/') else: destination = self.extractor.basename() self.set_target(destination, checker) if os.path.isdir(self.extractor.target): os.rename(source, self.target) os.rmdir(self.extractor.target) else: os.rename(self.extractor.target, self.target) self.extractor.included_root = './' class EmptyHandler(object): target = '' def can_handle(contents, options): return contents == EMPTY can_handle = staticmethod(can_handle) def __init__(self, extractor, options): os.rmdir(extractor.target) def handle(self): pass class BombHandler(BaseHandler): def can_handle(contents, options): return True can_handle = staticmethod(can_handle) def organize(self): basename = self.extractor.basename() self.set_target(basename, self.extractor.name_checker) os.rename(self.extractor.target, self.target) class BasePolicy(object): try: size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)) width = struct.unpack("HHHH", size)[1] except IOError: width = 80 width = width - 1 choice_wrapper = textwrap.TextWrapper(width=width, initial_indent=' * ', subsequent_indent=' ', break_long_words=False) def __init__(self, options): self.current_policy = None if options.batch: self.permanent_policy = self.answers[''] else: self.permanent_policy = None def ask_question(self, question): question = question + ["You can:"] for choice in self.choices: question.extend(self.choice_wrapper.wrap(choice)) while True: print "\n".join(question) try: answer = raw_input(self.prompt) except EOFError: return self.answers[''] try: return self.answers[answer.lower()] except KeyError: print def wrap(self, question, *args): words = question.split() for arg in args: words[words.index('%s')] = arg result = [words.pop(0)] for word in words: extend = '%s %s' % (result[-1], word) if len(extend) > self.width: result.append(word) else: result[-1] = extend return result def __cmp__(self, other): return cmp(self.current_policy, other) class OneEntryPolicy(BasePolicy): answers = {'h': EXTRACT_HERE, 'i': EXTRACT_WRAP, 'r': EXTRACT_RENAME, '': EXTRACT_WRAP} choice_template = ["extract the %s _I_nside a new directory named %s", "extract the %s and _R_ename it %s", "extract the %s _H_ere"] prompt = "What do you want to do? (I/r/h) " def __init__(self, options): BasePolicy.__init__(self, options) if options.flat: default = 'h' elif options.one_entry_default is not None: default = options.one_entry_default.lower() else: return if 'here'.startswith(default): self.permanent_policy = EXTRACT_HERE elif 'rename'.startswith(default): self.permanent_policy = EXTRACT_RENAME elif 'inside'.startswith(default): self.permanent_policy = EXTRACT_WRAP elif default is not None: raise ValueError("bad value %s for default policy" % (default,)) def prep(self, archive_filename, extractor): question = self.wrap( "%s contains one %s but its name doesn't match.", archive_filename, extractor.content_type) question.append(" Expected: " + extractor.basename()) question.append(" Actual: " + extractor.content_name) choice_vars = (extractor.content_type, extractor.basename()) self.choices = [text % choice_vars[:text.count('%s')] for text in self.choice_template] self.current_policy = (self.permanent_policy or self.ask_question(question)) def ok_for_match(self): return self.current_policy in (EXTRACT_RENAME, EXTRACT_HERE) class RecursionPolicy(BasePolicy): answers = {'o': RECURSE_ONCE, 'a': RECURSE_ALWAYS, 'n': RECURSE_NOT_NOW, 'v': RECURSE_NEVER, 'l': RECURSE_LIST, '': RECURSE_NOT_NOW} choices = ["_A_lways extract included archives during this session", "extract included archives this _O_nce", "choose _N_ot to extract included archives this once", "ne_V_er extract included archives during this session", "_L_ist included archives"] prompt = "What do you want to do? (a/o/N/v/l) " def __init__(self, options): BasePolicy.__init__(self, options) if options.show_list: self.permanent_policy = RECURSE_NEVER elif options.recursive: self.permanent_policy = RECURSE_ALWAYS def prep(self, current_filename, target, extractor): archive_count = len(extractor.included_archives) if ((self.permanent_policy is not None) or ((archive_count * 10) <= extractor.file_count)): self.current_policy = self.permanent_policy or RECURSE_NOT_NOW return question = self.wrap( "%s contains %s other archive file(s), out of %s file(s) total.", current_filename, archive_count, extractor.file_count) if target == '.': target = '' included_root = extractor.included_root if included_root == './': included_root = '' while True: self.current_policy = self.ask_question(question) if self.current_policy != RECURSE_LIST: break print ("\n%s\n" % '\n'.join([os.path.join(target, included_root, filename) for filename in extractor.included_archives])) if self.current_policy in (RECURSE_ALWAYS, RECURSE_NEVER): self.permanent_policy = self.current_policy def ok_to_recurse(self): return self.current_policy in (RECURSE_ALWAYS, RECURSE_ONCE) class ExtractorBuilder(object): extractor_map = {'tar': {'extractors': (TarExtractor,), 'mimetypes': ('x-tar',), 'extensions': ('tar',), 'magic': ('POSIX tar archive',)}, 'zip': {'extractors': (ZipExtractor, SevenExtractor), 'mimetypes': ('zip',), 'extensions': ('zip',), 'magic': ('(Zip|ZIP self-extracting) archive',)}, 'lzh': {'extractors': (LZHExtractor,), 'mimetypes': ('x-lzh', 'x-lzh-compressed'), 'extensions': ('lzh', 'lha'), 'magic': ('LHa [\d\.\?]+ archive',)}, 'rpm': {'extractors': (RPMExtractor,), 'mimetypes': ('x-redhat-package-manager', 'x-rpm'), 'extensions': ('rpm',), 'magic': ('RPM',)}, 'deb': {'extractors': (DebExtractor,), 'metadata': (DebMetadataExtractor,), 'mimetypes': ('x-debian-package',), 'extensions': ('deb',), 'magic': ('Debian binary package',)}, 'cpio': {'extractors': (CpioExtractor,), 'mimetypes': ('x-cpio',), 'extensions': ('cpio',), 'magic': ('cpio archive',)}, 'gem': {'extractors': (GemExtractor,), 'metadata': (GemMetadataExtractor,), 'mimetypes': ('x-ruby-gem',), 'extensions': ('gem',)}, '7z': {'extractors': (SevenExtractor,), 'mimetypes': ('x-7z-compressed',), 'extensions': ('7z',), 'magic': ('7-zip archive',)}, 'cab': {'extractors': (CABExtractor,), 'mimetypes': ('x-cab',), 'extensions': ('cab',), 'magic': ('Microsoft Cabinet Archive',)}, 'rar': {'extractors': (RarExtractor,), 'mimetypes': ('rar',), 'extensions': ('rar',), 'magic': ('RAR archive',)}, 'shield': {'extractors': (ShieldExtractor,), 'mimetypes': ('x-cab',), 'extensions': ('cab', 'hdr'), 'magic': ('InstallShield CAB',)}, 'msi': {'extractors': (SevenExtractor,), 'mimetypes': ('x-msi', 'x-ole-storage'), 'extensions': ('msi',), 'magic': ('Application: Windows Installer',)}, 'compress': {'extractors': (CompressionExtractor,)} } mimetype_map = {} magic_mime_map = {} extension_map = {} for ext_name, ext_info in extractor_map.items(): for mimetype in ext_info.get('mimetypes', ()): if '/' not in mimetype: mimetype = 'application/' + mimetype mimetype_map[mimetype] = ext_name for magic_re in ext_info.get('magic', ()): magic_mime_map[re.compile(magic_re)] = ext_name for extension in ext_info.get('extensions', ()): extension_map.setdefault(extension, []).append((ext_name, None)) for mapping in (('tar', 'bzip2', 'tar.bz2', 'tbz2', 'tb2', 'tbz'), ('tar', 'gzip', 'tar.gz', 'tgz'), ('tar', 'lzma', 'tar.lzma', 'tlz'), ('tar', 'xz', 'tar.xz'), ('tar', 'lz', 'tar.lz'), ('tar', 'compress', 'tar.Z', 'taz'), ('compress', 'gzip', 'Z', 'gz'), ('compress', 'bzip2', 'bz2'), ('compress', 'lzma', 'lzma'), ('compress', 'xz', 'xz')): for extension in mapping[2:]: extension_map.setdefault(extension, []).append(mapping[:2]) magic_encoding_map = {} for mapping in (('bzip2', 'bzip2 compressed'), ('gzip', 'gzip compressed'), ('lzma', 'LZMA compressed'), ('lzip', 'lzip compressed'), ('xz', 'xz compressed')): for pattern in mapping[1:]: magic_encoding_map[re.compile(pattern)] = mapping[0] def __init__(self, filename, options): self.filename = filename self.options = options def build_extractor(self, archive_type, encoding): type_info = self.extractor_map[archive_type] if self.options.metadata and type_info.has_key('metadata'): extractors = type_info['metadata'] else: extractors = type_info['extractors'] for extractor in extractors: yield extractor(self.filename, encoding) def get_extractor(self): tried_types = set() # As smart as it is, the magic test can't go first, because at least # on my system it just recognizes gem files as tar files. I guess # it's possible for the opposite problem to occur -- where the mimetype # or extension suggests something less than ideal -- but it seems less # likely so I'm sticking with this. for func_name in ('mimetype', 'extension', 'magic'): logger.debug("getting extractors by %s" % (func_name,)) extractor_types = \ getattr(self, 'try_by_' + func_name)(self.filename) logger.debug("done getting extractors") for ext_args in extractor_types: if ext_args in tried_types: continue tried_types.add(ext_args) logger.debug("trying %s extractor from %s" % (ext_args, func_name)) for extractor in self.build_extractor(*ext_args): yield extractor def try_by_mimetype(cls, filename): mimetype, encoding = mimetypes.guess_type(filename) try: return [(cls.mimetype_map[mimetype], encoding)] except KeyError: if encoding: return [('compress', encoding)] return [] try_by_mimetype = classmethod(try_by_mimetype) def magic_map_matches(cls, output, magic_map): return [result for regexp, result in magic_map.items() if regexp.search(output)] magic_map_matches = classmethod(magic_map_matches) def try_by_magic(cls, filename): process = subprocess.Popen(['file', '-z', filename], stdout=subprocess.PIPE) status = process.wait() if status != 0: return [] output = process.stdout.readline() process.stdout.close() if output.startswith('%s: ' % filename): output = output[len(filename) + 2:] mimes = cls.magic_map_matches(output, cls.magic_mime_map) encodings = cls.magic_map_matches(output, cls.magic_encoding_map) if mimes and not encodings: encodings = [None] elif encodings and not mimes: mimes = ['compress'] return [(m, e) for m in mimes for e in encodings] try_by_magic = classmethod(try_by_magic) def try_by_extension(cls, filename): parts = filename.split('.')[-2:] results = [] while parts: results.extend(cls.extension_map.get('.'.join(parts), [])) del parts[0] return results try_by_extension = classmethod(try_by_extension) class BaseAction(object): def __init__(self, options, filenames): self.options = options self.filenames = filenames self.target = None self.do_print = False def report(self, function, *args): try: error = function(*args) except EXTRACTION_ERRORS, exception: error = str(exception) logger.debug(''.join(traceback.format_exception(*sys.exc_info()))) return error def show_filename(self, filename): if len(self.filenames) < 2: return elif self.do_print: print else: self.do_print = True print "%s:" % (filename,) class ExtractionAction(BaseAction): handlers = [FlatHandler, OverwriteHandler, MatchHandler, EmptyHandler, BombHandler] def get_handler(self, extractor): if extractor.content_type in ONE_ENTRY_UNKNOWN: self.options.one_entry_policy.prep(self.current_filename, extractor) for handler in self.handlers: if handler.can_handle(extractor.content_type, self.options): logger.debug("using %s handler" % (handler.__name__,)) self.current_handler = handler(extractor, self.options) break def show_extraction(self, extractor): if self.options.log_level > logging.INFO: return self.show_filename(self.current_filename) if extractor.contents is None: print self.current_handler.target return def reverser(x, y): return cmp(y, x) if self.current_handler.target == '.': filenames = extractor.contents filenames.sort(reverser) else: filenames = [self.current_handler.target] pathjoin = os.path.join isdir = os.path.isdir while filenames: filename = filenames.pop() if isdir(filename): print "%s/" % (filename,) new_filenames = os.listdir(filename) new_filenames.sort(reverser) filenames.extend([pathjoin(filename, new_filename) for new_filename in new_filenames]) else: print filename def run(self, filename, extractor): self.current_filename = filename error = (self.report(extractor.extract) or self.report(self.get_handler, extractor) or self.report(self.current_handler.handle) or self.report(self.show_extraction, extractor)) if not error: self.target = self.current_handler.target return error class ListAction(BaseAction): def list_filenames(self, extractor, filename): # We get a line first to make sure there's not going to be some # basic error before we show what filename we're listing. filename_lister = extractor.get_filenames() try: first_line = filename_lister.next() except StopIteration: self.show_filename(filename) else: self.did_list = True self.show_filename(filename) print first_line for line in filename_lister: print line def run(self, filename, extractor): self.did_list = False error = self.report(self.list_filenames, extractor, filename) if error and self.did_list: logger.error("lister failed: ignore above listing for %s" % (filename,)) return error class ExtractorApplication(object): def __init__(self, arguments): for signal_num in (signal.SIGINT, signal.SIGTERM): signal.signal(signal_num, self.abort) signal.signal(signal.SIGPIPE, signal.SIG_DFL) self.parse_options(arguments) self.setup_logger() self.successes = [] self.failures = [] def clean_destination(self, dest_name): try: os.unlink(dest_name) except OSError, error: if error.errno == errno.EISDIR: shutil.rmtree(dest_name, ignore_errors=True) def abort(self, signal_num, frame): signal.signal(signal_num, signal.SIG_IGN) print logger.debug("traceback:\n" + ''.join(traceback.format_stack(frame)).rstrip()) logger.debug("got signal %s" % (signal_num,)) try: basename = self.current_extractor.target except AttributeError: basename = None if basename is not None: logger.debug("cleaning up %s" % (basename,)) clean_targets = set([os.path.realpath('.')]) if hasattr(self, 'current_directory'): clean_targets.add(os.path.realpath(self.current_directory)) for directory in clean_targets: self.clean_destination(os.path.join(directory, basename)) sys.exit(1) def parse_options(self, arguments): parser = optparse.OptionParser( usage="%prog [options] archive [archive2 ...]", description="Intelligent archive extractor", version=VERSION_BANNER ) parser.add_option('-l', '-t', '--list', '--table', dest='show_list', action='store_true', default=False, help="list contents of archives on standard output") parser.add_option('-m', '--metadata', dest='metadata', action='store_true', default=False, help="extract metadata from a .deb/.gem") parser.add_option('-r', '--recursive', dest='recursive', action='store_true', default=False, help="extract archives contained in the ones listed") parser.add_option('--one', '--one-entry', dest='one_entry_default', default=None, help=("specify extraction policy for one-entry " + "archives: inside/rename/here")) parser.add_option('-n', '--noninteractive', dest='batch', action='store_true', default=False, help="don't ask how to handle special cases") parser.add_option('-o', '--overwrite', dest='overwrite', action='store_true', default=False, help="overwrite any existing target output") parser.add_option('-f', '--flat', '--no-directory', dest='flat', action='store_true', default=False, help="extract everything to the current directory") parser.add_option('-v', '--verbose', dest='verbose', action='count', default=0, help="be verbose/print debugging information") parser.add_option('-q', '--quiet', dest='quiet', action='count', default=3, help="suppress warning/error messages") self.options, filenames = parser.parse_args(arguments) if not filenames: parser.error("you did not list any archives") # This makes WARNING is the default. self.options.log_level = (10 * (self.options.quiet - self.options.verbose)) try: self.options.one_entry_policy = OneEntryPolicy(self.options) except ValueError: parser.error("invalid value for --one-entry option") self.options.recursion_policy = RecursionPolicy(self.options) self.archives = {os.path.realpath(os.curdir): filenames} def setup_logger(self): logging.getLogger().setLevel(self.options.log_level) handler = logging.StreamHandler() handler.setLevel(self.options.log_level) formatter = logging.Formatter("dtrx: %(levelname)s: %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) logger.debug("logger is set up") def recurse(self, filename, extractor, action): self.options.recursion_policy.prep(filename, action.target, extractor) if self.options.recursion_policy.ok_to_recurse(): for filename in extractor.included_archives: logger.debug("recursing with %s archive" % (extractor.content_type,)) tail_path, basename = os.path.split(filename) path_args = [self.current_directory, extractor.included_root, tail_path] logger.debug("included root: %s" % (extractor.included_root,)) logger.debug("tail path: %s" % (tail_path,)) if os.path.isdir(action.target): logger.debug("action target: %s" % (action.target,)) path_args.insert(1, action.target) directory = os.path.join(*path_args) self.archives.setdefault(directory, []).append(basename) def check_file(self, filename): try: result = os.stat(filename) except OSError, error: return error.strerror if stat.S_ISDIR(result.st_mode): return "cannot work with a directory" def show_stderr(self, logger_func, stderr): if stderr: logger_func("Error output from this process:\n" + stderr.rstrip('\n')) def try_extractors(self, filename, builder): errors = [] for extractor in builder: self.current_extractor = extractor # For the abort() method. error = self.action.run(filename, extractor) if error: errors.append((extractor.file_type, extractor.encoding, error, extractor.get_stderr())) if extractor.target is not None: self.clean_destination(extractor.target) else: self.show_stderr(logger.warn, extractor.get_stderr()) self.recurse(filename, extractor, self.action) return logger.error("could not handle %s" % (filename,)) if not errors: logger.error("not a known archive type") return True for file_type, encoding, error, stderr in errors: message = ["treating as", file_type, "failed:", error] if encoding: message.insert(1, "%s-encoded" % (encoding,)) logger.error(' '.join(message)) self.show_stderr(logger.error, stderr) return True def download(self, filename): url = filename.lower() for protocol in 'http', 'https', 'ftp': if url.startswith(protocol + '://'): break else: return filename, None # FIXME: This can fail if there's already a file in the directory # that matches the basename of the URL. status = subprocess.call(['wget', '-c', filename], stdin=subprocess.PIPE) if status != 0: return None, "wget returned status code %s" % (status,) return os.path.basename(urlparse.urlparse(filename)[2]), None def run(self): if self.options.show_list: action = ListAction else: action = ExtractionAction self.action = action(self.options, self.archives.values()[0]) while self.archives: self.current_directory, self.filenames = self.archives.popitem() os.chdir(self.current_directory) for filename in self.filenames: filename, error = self.download(filename) if not error: builder = ExtractorBuilder(filename, self.options) error = (self.check_file(filename) or self.try_extractors(filename, builder.get_extractor())) if error: if error != True: logger.error("%s: %s" % (filename, error)) self.failures.append(filename) else: self.successes.append(filename) self.options.one_entry_policy.permanent_policy = EXTRACT_WRAP if self.failures: return 1 return 0 if __name__ == '__main__': app = ExtractorApplication(sys.argv[1:]) sys.exit(app.run()) dtrx-7.1/NEWS0000600000175000017500000001735411662032315012064 0ustar brettbrettChanges in dtrx =============== Version 7.1 ----------- New features ~~~~~~~~~~~~ * LZH archives are now supported. Bug fixes ~~~~~~~~~ * dtrx will no longer offer to extract the zero archive files found in a zero-file archive. * Temporary directories will be cleaned up after extracting an empty archive. Version 7.0 ----------- At this point, I consider dtrx to be mature software. It's maybe a little too interactive, but otherwise it does everything I want, and it does it very well. Expect new releases to be few and far between going forward. New features ~~~~~~~~~~~~ * If any of dtrx's command line arguments are URLs, it will automatically download them with `wget -c` in the current directory before extracting them. See the documentation for more information about this feature. Note that there might be trouble if there's already a file in the directory where wget would normally save the download. Enhancements ~~~~~~~~~~~~ * dtrx will try to extract ZIP files with 7z if unzip is not successful. Thanks to Edward H for reporting this bug. * dtrx will be smarter about removing extensions from filenames when extracting to a new directory or file. * dtrx will not ask you if you want to recurse through an archive if the number of archives inside the original file is small. Version 6.6 ----------- Enhancements ~~~~~~~~~~~~ * dtrx can now handle `xz compression`_. .. _xz compression: http://tukaani.org/xz/ Other changes ~~~~~~~~~~~~~ * The tests now use the PyYAML library, instead of the abandoned Syck. Thanks to Miguelangel Jose Freitas Loreto for a patch. Version 6.5 ----------- Enhancements ~~~~~~~~~~~~ * When you list archive contents with -l or -t, dtrx will start printing results much faster than it used to. There's a small chance that it will print some incorrect listings if it misdetects the archive type of a given file, but it will show you an error message when that happens. * dtrx recognizes more kinds of compressed tar archives by their extension. * You can now extract newer .deb packages that are compressed with bzip2 or lzma. Bug fixes ~~~~~~~~~ * When extracting an archive that contained a file with a mismatched filename, the prompt would offer you a chance to "rename the directory" instead of "rename the file." This wording has been fixed, along with some other wording adjustments in the prompts generally. * Perform more reliable detection of the terminal size, and improve word wrapping on prompts. Other changes ~~~~~~~~~~~~~ * The README is now written like a man page, and can be converted to a man page by using rst2man_. .. _rst2man: http://docutils.sourceforge.net/sandbox/manpage-writer/ Version 6.4 ----------- Enhancements ~~~~~~~~~~~~ * Support detection of LZMA archives by magic. * Interactive prompts are wrapped much more cleanly. Bug fixes ~~~~~~~~~ * Fix a bug where dtrx would crash when extracting an archive with no files inside it. Version 6.3 ----------- New features ~~~~~~~~~~~~ * Add support for RAR archives. Thanks to Peter Kelemen for the patch. Bug fixes ~~~~~~~~~ * Previous versions of dtrx would fail to extract certain archive types with the ``-v`` option specified. This has been fixed. * dtrx 6.3 no longer imports the sets module unless it's running under a very old version of Python, to avoid deprecation warnings under Python 2.6. Version 6.2 ----------- New features ~~~~~~~~~~~~ * --one-entry option: Normally, if an archive only contains one file or directory with a name that doesn't match the archive's, dtrx will ask you how to handle it. With this option, you can specify ahead of time what should happen. Bug fixes ~~~~~~~~~ * Since version 6.0, when you extracted or listed the contents of a cpio archive, dtrx would display a warning that simply said "1234 blocks." dtrx 6.2 suppresses this message. * When you try to list the contents of an archive, dtrx will now cope with misnamed files more gracefully, giving more accurate results and showing fewer error messages. * dtrx 6.2 will only show you error messages from archive extraction if it is completely unable to extract the file. If one of its extraction methods succeeds, it will no longer show you the error messages from previous extraction attempts. * dtrx is now better about cleaning up partially extracted archives when it encounters an error or signal. * Users will no longer see error messages about broken pipes from dtrx. Version 6.1 ----------- New features ~~~~~~~~~~~~ * Add support for InstallShield archives, using the unshield command. * The wording of many of the interactive prompts has been adjusted, hopefully to be clearer and provide more information to the user immediately. Bug fixes ~~~~~~~~~ * dtrx 6.1 does a better job protecting against race conditions when extracting a single file. * If you used the -f option, and extracted an archive that only contained one file or directory, dtrx 6.0 would still prompt you to ask how it should be extracted. dtrx 6.1 fixes this, extracting the contents to the current directory as -f requires. * Recursive extraction would not work well in dtrx 6.0 when the contents of the original archive were a single file. This has been fixed in dtrx 6.1. Version 6.0 ----------- New features ~~~~~~~~~~~~ * When you specify -v at the command line, dtrx will display the files it extracts, much like tar. * When dtrx prompts you about how to handle recursive archives, you now have the option of listing what those archives before making a decision. * dtrx will now provide more information about why a particular extraction attempt failed. It will show you error messages from all the attempts it made, rather than only the last error it got. It will also detect and warn you when one of the underlying extraction tools, like cabextract, cannot be found. * dtrx does a better job of cleaning up after itself. It wouldn't always clean up temporary files after certain errors; that has been fixed. It also catches SIGINT and SIGTERM and cleans up before finishing execution. Bug fixes ~~~~~~~~~ * Version 5.0 introduced a regression such that dtrx would not offer to extract recursive archives that were hidden under subdirectories. Version 6.0 fixes that. * dtrx would not properly extract recursive archives when the original archive contained a single directory. This has been fixed. Version 5.1 ----------- Bug fixes ~~~~~~~~~ * Version 5.0 did not work with Python 2.3; it used a new language feature. This release fixes that. Version 5.0 ----------- New features ~~~~~~~~~~~~ * dtrx can now extract Ruby gems, 7z archives, and Microsoft Cabinet archives. It can also handle files compressed with lzma, and extract the metadata from Debian packages and Ruby gems. * dtrx will now use several strategies to try to figure out what kind of file you have, and extract it accordingly. If one doesn't work, it'll try something else if it can. * dtrx now displays more helpful errors when things go wrong. * Previous versions of dtrx would look at what files were included in an archive, and then make a decision about how to extract it. Now, it always extracts files to a temporary directory, and figures out what to do with that directory afterward. This should be slightly faster and nicer to the system. Version 4.0 ----------- New features ~~~~~~~~~~~~ * dtrx is now interactive. If the archive only contains one item, or contains other archives, dtrx will ask you how you would like to handle it. You can turn these questions off the the -n option. * There is a new -l option, which simply lists the archive's contents rather than extracting them. dtrx-7.1/COPYING0000600000175000017500000010451311662032265012416 0ustar brettbrett GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . dtrx-7.1/setup.py0000600000175000017500000000325411662032521013070 0ustar brettbrett#!/usr/bin/env python from distutils.core import setup setup(name="dtrx", version = "7.1", description = "Script to intelligently extract multiple archive types", author = "Brett Smith", author_email = "brettcsmith@brettcsmith.org", url = "http://www.brettcsmith.org/2007/dtrx/", download_url = "http://www.brettcsmith.org/2007/dtrx/", scripts = ['scripts/dtrx'], license = "GNU General Public License, version 3 or later", classifiers = ['Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Natural Language :: English', 'Operating System :: POSIX', 'Programming Language :: Python', 'Topic :: Utilities'], long_description = """dtrx extracts archives in a number of different formats; it currently supports tar, zip (including self-extracting .exe files), cpio, rpm, deb, gem, 7z, cab, rar, and InstallShield files. It can also decompress files compressed with gzip, bzip2, lzma, xz, or compress. In addition to providing one command to handle many different archive types, dtrx also aids the user by extracting contents consistently. By default, everything will be written to a dedicated directory that's named after the archive. dtrx will also change the permissions to ensure that the owner can read and write all those files.""" ) dtrx-7.1/README0000600000175000017500000001041611662032572012242 0ustar brettbrett==== dtrx ==== ---------------------------------- cleanly extract many archive types ---------------------------------- :Author: Brett Smith :Date: 2011-11-19 :Copyright: dtrx 7.1 is copyright © 2006-2011 Brett Smith and others. Feel free to send comments, bug reports, patches, and so on. You can find the latest version of dtrx on its home page at . dtrx is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . :Version: 7.1 :Manual section: 1 SYNOPSIS ======== dtrx [OPTIONS] ARCHIVE [ARCHIVE ...] DESCRIPTION =========== dtrx extracts archives in a number of different formats; it currently supports tar, zip (including self-extracting .exe files), cpio, rpm, deb, gem, 7z, cab, rar, lzh, and InstallShield files. It can also decompress files compressed with gzip, bzip2, lzma, xz, or compress. In addition to providing one command to handle many different archive types, dtrx also aids the user by extracting contents consistently. By default, everything will be written to a dedicated directory that's named after the archive. dtrx will also change the permissions to ensure that the owner can read and write all those files. To run dtrx, simply call it with the archive(s) you wish to extract as arguments. For example:: $ dtrx coreutils-5.*.tar.gz You may specify URLs as arguments as well. If you do, dtrx will use `wget -c` to download the URL to the current directory and then extract what it downloads. This may fail if you already have a file in the current directory with the same name as the file you're trying to download. OPTIONS ======= dtrx supports a number of options to mandate specific behavior: -r, --recursive With this option, dtrx will search inside the archives you specify to see if any of the contents are themselves archives, and extract those as well. --one, --one-entry Normally, if an archive only contains one file or directory with a name that doesn't match the archive's, dtrx will ask you how to handle it. With this option, you can specify ahead of time what should happen. Possible values are: inside Extract the file/directory inside another directory named after the archive. This is the default. rename Extract the file/directory in the current directory, and then rename it to match the name of the archive. here Extract the file/directory in the current directory. -o, --overwrite Normally, dtrx will avoid extracting into a directory that already exists, and instead try to find an alternative name to use. If this option is listed, dtrx will use the default directory name no matter what. -f, --flat Extract all archive contents into the current directory, instead of their own dedicated directory. This is handy if you have multiple archive files which all need to be extracted into the same directory structure. Note that existing files may be overwritten with this option. -n, --noninteractive dtrx will normally ask the user how to handle certain corner cases, such as how to handle an archive that only contains one file. This option suppresses those questions; dtrx will instead use sane, conservative defaults. -l, -t, --list, --table Don't extract the archives; just list their contents on standard output. -m, --metadata Extract the metadata from .deb and .gem archives, instead of their normal contents. -q, --quiet Suppress warning messages. List this option twice to make dtrx silent. -v, --verbose Show the files that are being extracted. List this option twice to print debugging information. --help Display basic help. --version Display dtrx's version, copyright, and license information. dtrx-7.1/INSTALL0000600000175000017500000000375711662032315012420 0ustar brettbrettdtrx Installation Documentation =============================== Requirements ------------ dtrx will work out of the box with Python_ 2.4 or greater. You can also use Python 2.3 if you separately install the `subprocess module`_. .. _Python: http://www.python.org/ .. _`subprocess module`: http://www.lysator.liu.se/~astrand/popen5/ dtrx calls out to different external tools to support different archive types. Most of these are already installed on most GNU/Linux systems, so you probably won't have to worry about these too much, but just for completeness, the exact requirements for each format are as follows: tar archives tar zip archives unzip, zipinfo cpio archives cpio rpm archives rpm2cpio, cpio deb archives ar, tar, zcat, bzcat, lzcat gem archives tar, zcat 7z archives 7z lzh archives lha Microsoft Cabinet archives cabextract InstallShield archives unshield rar archives unrar Files compressed with gzip or compress zcat Files compressed with bzip2 bzcat Files compressed with lzma lzcat Files compressed with xz xzcat Installation ------------ dtrx is just a simple script, making it easy to stash wherever you need it. Just copy ``scripts/dtrx`` to a location that's convenient for you. If you'd like to install the program system-wide, you can run the following as root or equivalent:: python setup.py install --prefix=/usr/local Running Tests ------------- dtrx comes with a suite of tests that are designed to ensure it's running properly. If you'd like, you can run these tests on your own system. Simply run the following command from the dtrx source directory:: python tests/compare.py To run the tests, you'll need the `PyYAML module`_. .. _PyYAML module: http://pyyaml.org/ If everything's in order, all the tests should pass. Note that some of them will fail if some of the programs listed above aren't installed on your system. Many of the tests will fail if for some reason you're missing the very common commands, like tar and zcat. dtrx-7.1/tests/0000700000175000017500000000000011662032736012522 5ustar brettbrettdtrx-7.1/tests/test-recursive-badperms.tar.bz20000600000175000017500000000031611662032265020504 0ustar brettbrettBZh91AY&SYb=%+ِ@@wf^ 0d FzL4IeI=Cih/A P#1< W 2a嬖A/0 @Mb)u,0$ 9$#w\3YA7ֱpIxmM=ÞM61Ew$S V#Pdtrx-7.1/tests/test-1.23.tar.lzma0000600000175000017500000000030111662032265015525 0ustar brettbrett](:J|Ү.[=_NΒB:aye{cBm L=VP[bI$o!wۭZئqc2_˩#YbM؊VKtyDQYej}K" hrk= 0.iy3e2j5>\ВQ<@cdtrx-7.1/tests/test-1.23.tar.gz0000600000175000017500000000033411662032265015210 0ustar brettbrettհCEK0@. }@ݿMt@6mCt'UE(xD{"Yz[Z]ԎMOͼOTzj'О{7N@QrVY#syjtݼöWiakQ?`RW(dtrx-7.1/tests/test-recursive-no-prompt.tar.bz20000600000175000017500000000044711662032265020647 0ustar brettbrettBZh91AY&SY. Q0AH0B^8b@D0jhOQmF@ zjoٿ fυLJ^X|E[u^<2` P  1 P\E(R: SyI)vMfj[S"bt3*EG;6 " NQj6aC!2 JFzTj2gZRs2%2?ը3$knCc5t9uA debian-binary/ 1239587974 1000 1000 100644 4 ` 2.0 control.tar.gz/ 1239587974 1000 1000 100644 220 ` հCEK0@. }@ݿMt@6mCt'UE(xD{"Yz[Z]ԎMOͼOTzj'О{7N@QrVY#syjtݼöWiakQ?`RW(data.tar.lzma/ 1239587974 1000 1000 100644 178 ` ](}Zlymy uQy!x/Knf jgE.arYq8hc/. X39$jB,ztpQTSyn&JbWmFS>: RGL:4>Ӟre Q~sJ(dtrx-7.1/tests/test-onedir.tar.gz0000600000175000017500000000023611662032265016106 0ustar brettbrettxfEK @Q$NCj!Ntp! eeRc&ӧc51NVN8]/uh|9wm2awZSh ^i3Eo/ӽ;~23OtG(dtrx-7.1/tests/test-dot-first-onedir.tar.gz0000600000175000017500000000022711662032265020017 0ustar brettbrett*FM @ =ŜM x"X "K"hURxO^L ޒY Nr1;'SK0Sov5Ә)aͿC<ر;σC(dtrx-7.1/tests/test-dot-first-bomb.tar.gz0000600000175000017500000000022111662032265017450 0ustar brettbrett*F= 0 a9OHΓ@\u.]Z:!> $_ۭ&uiUj>՗QL jKw=ܮyN!Ȓ.9?p[TyOH|(dtrx-7.1/tests/test-1.23.cpio0000600000175000017500000000100011662032265014724 0ustar brettbrettqACE test-1.23qACE test-1.23/1qACEtest-1.23/1/2qCEtest-1.23/1/2/3qACE test-1.23/aqCEtest-1.23/a/bqCEtest-1.23/foobarq TRAILER!!!dtrx-7.1/tests/test-1.23.tar0000600000175000017500000002400011662032265014565 0ustar brettbretttest-1.23/0000755000175000017500000000000010520721673011443 5ustar brettbretttest-1.23/1/0000755000175000017500000000000010520721657011605 5ustar brettbretttest-1.23/1/2/0000755000175000017500000000000010520721664011744 5ustar brettbretttest-1.23/1/2/30000644000175000017500000000000010520721664012017 0ustar brettbretttest-1.23/a/0000755000175000017500000000000010520721671011661 5ustar brettbretttest-1.23/a/b0000644000175000017500000000000010520721671012013 0ustar brettbretttest-1.23/foobar0000644000175000017500000000000010520721673012624 0ustar brettbrettdtrx-7.1/tests/tests.yml0000600000175000017500000003650111662032315014407 0ustar brettbrett- name: basic .tar filenames: test-1.23.tar baseline: | tar -xf $1 - name: basic .tar.gz filenames: test-1.23.tar.gz baseline: | tar -zxf $1 - name: basic .tar.bz2 filenames: test-1.23.tar.bz2 baseline: | mkdir test-1.23 cd test-1.23 tar -jxf ../$1 - name: basic .zip filenames: test-1.23.zip baseline: | mkdir test-1.23 cd test-1.23 unzip -q ../$1 - name: basic .lzh filenames: test-1.23.lzh baseline: | mkdir test-1.23 cd test-1.23 lha xq ../$1 - name: basic .deb filenames: test-1.23_all.deb baseline: | mkdir test-1.23 cd test-1.23 ar p ../$1 data.tar.gz | tar -zx - name: .deb with LZMA compression filenames: test-2_all.deb baseline: | mkdir test-2 cd test-2 ar p ../$1 data.tar.lzma | lzcat | tar -x - name: basic .gem filenames: test-1.23.gem baseline: | mkdir test-1.23 cd test-1.23 tar -xOf ../$1 data.tar.gz | tar -zx - name: basic .7z filenames: test-1.23.7z baseline: | 7z x $1 - name: basic .lzma filenames: test-1.23.tar.lzma baseline: | lzcat $1 | tar -x - name: basic .cpio filenames: test-1.23.cpio baseline: | cpio -i --make-directories <$1 antigrep: blocks? - name: .deb metadata filenames: test-1.23_all.deb options: --metadata baseline: | mkdir test-1.23 cd test-1.23 ar p ../$1 control.tar.gz | tar -zx - name: .gem metadata filenames: test-1.23.gem options: -m baseline: | tar -xOf $1 metadata.gz | zcat > test-1.23.gem-metadata.txt cleanup: rm -f test-1.23.gem-metadata.txt posttest: | exec [ "$(cat test-1.23.gem-metadata.txt)" = "hi" ] - name: recursion and permissions filenames: test-recursive-badperms.tar.bz2 options: -n -r baseline: | extract() { mkdir "$1" cd "$1" tar "-${3}xf" "../$2" } extract test-recursive-badperms "$1" j extract test-badperms test-badperms.tar chmod 700 testdir posttest: | exec [ "$(cat test-recursive-badperms/test-badperms/testdir/testfile)" = \ "hey" ] - name: decompressing gz, not interactive directory: inside-dir filenames: ../test-text.gz options: "" antigrep: "." baseline: | zcat $1 >test-text posttest: | exec [ "$(cat test-text)" = "hi" ] - name: decompressing bz2, not interactive directory: inside-dir filenames: ../test-text.bz2 options: "" antigrep: "." baseline: | bzcat $1 >test-text posttest: | exec [ "$(cat test-text)" = "hi" ] - name: decompressing xz, not interactive directory: inside-dir filenames: ../test-text.xz options: "" antigrep: "." baseline: | xzcat $1 >test-text posttest: | exec [ "$(cat test-text)" = "hi" ] - name: decompressing lzip, not interactive directory: inside-dir filenames: ../test-text.lz options: "" antigrep: "." baseline: | lzip -cd <$1 >test-text posttest: | exec [ "$(cat test-text)" = "hi" ] - name: decompression with -r directory: inside-dir filenames: ../test-text.gz options: -n -r baseline: | zcat $1 >test-text - name: decompression with -fr directory: inside-dir filenames: ../test-text.gz options: -n -fr baseline: | zcat $1 >test-text - name: overwrite protection filenames: test-1.23.tar.bz2 baseline: | mkdir test-1.23.1 cd test-1.23.1 tar -jxf ../$1 prerun: | mkdir test-1.23 - name: overwrite option filenames: test-1.23.tar.bz2 options: -n -o baseline: | cd test-1.23 tar -jxf ../$1 prerun: | mkdir test-1.23 - name: flat option directory: inside-dir filenames: ../test-1.23.tar.bz2 options: -n -f baseline: | tar -jxf $1 - name: flat recursion and permissions directory: inside-dir filenames: ../test-recursive-badperms.tar.bz2 options: -n -fr baseline: | tar -jxf $1 tar -xf test-badperms.tar chmod 700 testdir posttest: | exec [ "$(cat testdir/testfile)" = "hey" ] - name: no files error: true grep: "[Uu]sage" - name: bad file error: true filenames: nonexistent-file.tar - name: not an archive error: true filenames: tests.yml - name: bad options options: -n --nonexistent-option filenames: test-1.23.tar error: true - name: --version options: -n --version grep: ersion \d+\.\d+ filenames: test-1.23.tar baseline: | exit 0 - name: one good archive of many filenames: tests.yml test-1.23.tar nonexistent-file.tar error: true baseline: | tar -xf $2 - name: silence filenames: tests.yml options: -n -qq error: true antigrep: '.' - name: can't write to directory directory: inside-dir filenames: ../test-1.23.tar error: true grep: ERROR antigrep: Traceback prerun: | chmod 500 . - name: list contents of one file options: -n -l filenames: test-1.23.tar output: | test-1.23/ test-1.23/1/ test-1.23/1/2/ test-1.23/1/2/3 test-1.23/a/ test-1.23/a/b test-1.23/foobar - name: list contents of LZH options: -n -l filenames: test-1.23.lzh output: | 1/ 1/2/ 1/2/3 a/ a/b foobar - name: list contents of .cpio options: -n -l filenames: test-1.23.cpio grep: ^test-1\.23/1/2/3$ antigrep: blocks? - name: list contents of multiple files options: -n --table filenames: test-1.23_all.deb test-1.23.zip output: | test-1.23_all.deb: 1/ 1/2/ 1/2/3 a/ a/b foobar test-1.23.zip: 1/2/3 a/b foobar - name: list contents of compressed file options: -n -t filenames: test-text.gz output: test-text - name: default behavior with one directory (gz) options: -n filenames: test-onedir.tar.gz baseline: | mkdir test-onedir cd test-onedir tar -zxf ../$1 - name: one directory extracted inside another interactively (gz) options: "" filenames: test-onedir.tar.gz grep: one directory input: i baseline: | mkdir test-onedir cd test-onedir tar -zxf ../$1 - name: one directory extracted with rename interactively (gz) options: "" filenames: test-onedir.tar.gz input: r baseline: | tar -zxf $1 mv test test-onedir - name: one directory extracted here interactively (gz) options: "" filenames: test-onedir.tar.gz input: h baseline: | tar -zxf $1 - name: --one=inside options: "--one=inside -n" filenames: test-onedir.tar.gz baseline: | mkdir test-onedir cd test-onedir tar -zxf ../$1 - name: --one=rename options: "--one-entry=rename -n" filenames: test-onedir.tar.gz baseline: | tar -zxf $1 mv test test-onedir - name: --one=here options: "--one=here -n" filenames: test-onedir.tar.gz baseline: | tar -zxf $1 - name: default behavior with one directory (bz2) options: -n filenames: test-onedir.tar.gz baseline: | mkdir test-onedir cd test-onedir tar -zxf ../$1 - name: one directory extracted inside another (bz2) options: "" filenames: test-onedir.tar.gz input: i baseline: | mkdir test-onedir cd test-onedir tar -zxf ../$1 - name: one directory extracted with rename (bz2) options: "" filenames: test-onedir.tar.gz input: r baseline: | tar -zxf $1 mv test test-onedir - name: one directory extracted here (bz2) options: "" filenames: test-onedir.tar.gz input: h baseline: | tar -zxf $1 - name: default behavior with one file options: -n filenames: test-onefile.tar.gz baseline: | mkdir test-onefile cd test-onefile tar -zxf ../$1 - name: one file extracted inside a directory options: "" filenames: test-onefile.tar.gz input: i grep: one file baseline: | mkdir test-onefile cd test-onefile tar -zxf ../$1 - name: prompt wording with one file options: "" filenames: test-onefile.tar.gz input: i grep: file _I_nside - name: one file extracted with rename, with Expected text options: "" filenames: test-onefile.tar.gz input: r grep: "Expected: test-onefile" baseline: | tar -zxOf $1 >test-onefile - name: one file extracted here, with Actual text options: "" filenames: test-onefile.tar.gz input: h grep: " Actual: test-text" baseline: | tar -zxf $1 - name: bomb with preceding dot in the table filenames: test-dot-first-bomb.tar.gz options: "" antigrep: one baseline: | mkdir test-dot-first-bomb cd test-dot-first-bomb tar -zxf ../$1 - name: one directory preceded by dot in the table filenames: test-dot-first-onedir.tar.gz options: "" grep: "Actual: (./)?dir/" input: h baseline: | tar -zxf $1 - name: two one-item archives with different answers filenames: test-onedir.tar.gz test-onedir.tar.gz options: "" input: | h r baseline: | tar -zxf $1 mv test test-onedir tar -zxf $1 - name: interactive recursion (always) filenames: test-recursive-badperms.tar.bz2 test-recursive-badperms.tar.bz2 options: "" input: | i a i baseline: | extract() { mkdir test-recursive-badperms$2 cd test-recursive-badperms$2 tar -jxf ../$1 mkdir test-badperms cd test-badperms tar -xf ../test-badperms.tar chmod 700 testdir cd ../.. } extract $1 extract $1 .1 - name: interactive recursion (once) filenames: test-recursive-badperms.tar.bz2 test-recursive-badperms.tar.bz2 options: "" input: | i o i n baseline: | extract() { mkdir "$1" cd "$1" tar "-${3}xf" "../$2" } extract test-recursive-badperms "$1" j extract test-badperms test-badperms.tar chmod 700 testdir cd ../.. extract test-recursive-badperms.1 "$1" j - name: interactive recursion (never) filenames: test-recursive-badperms.tar.bz2 test-recursive-badperms.tar.bz2 options: "" input: | i v i baseline: | extract() { mkdir test-recursive-badperms$2 cd test-recursive-badperms$2 tar -jxf ../$1 cd .. } extract $1 extract $1 .1 - name: recursion in subdirectories here filenames: test-deep-recursion.tar options: "" input: | h o grep: 'contains 2 other archive file\(s\), out of 2 file\(s\)' baseline: | tar -xf $1 cd subdir zcat test-text.gz > test-text cd subsubdir zcat test-text.gz > test-text - name: recursion in subdirectories with rename filenames: test-deep-recursion.tar options: "" input: | r o grep: "contains 2" baseline: | tar -xf $1 mv subdir test-deep-recursion cd test-deep-recursion zcat test-text.gz > test-text cd subsubdir zcat test-text.gz > test-text - name: recursion in subdirectories inside new dir filenames: test-deep-recursion.tar options: "" input: | i o grep: "contains 2" baseline: | mkdir test-deep-recursion cd test-deep-recursion tar -xf ../$1 cd subdir zcat test-text.gz > test-text cd subsubdir zcat test-text.gz > test-text - name: extracting file with bad extension filenames: test-1.23.bin prerun: cp ${1}test-1.23.tar.gz ${1}test-1.23.bin cleanup: rm -f ${1}test-1.23.bin baseline: | tar -zxf $1 - name: extracting file with misleading extension filenames: trickery.tar.gz prerun: cp ${1}test-1.23.zip ${1}trickery.tar.gz cleanup: rm -f ${1}trickery.tar.gz antigrep: '.' baseline: | mkdir trickery cd trickery unzip -q ../$1 - name: listing file with misleading extension options: -l filenames: trickery.tar.gz prerun: cp ${1}test-1.23.zip ${1}trickery.tar.gz cleanup: rm -f ${1}trickery.tar.gz grep: "^1/2/3$" antigrep: "^dtrx:" - name: listing multiple file with misleading extensions options: -l filenames: trickery.tar.gz trickery.tar.gz prerun: cp ${1}test-1.23.zip ${1}trickery.tar.gz cleanup: rm -f ${1}trickery.tar.gz output: | trickery.tar.gz: 1/2/3 a/b foobar trickery.tar.gz: 1/2/3 a/b foobar - name: non-archive error filenames: /dev/null error: true grep: "not a known archive type" - name: no such file error filenames: nonexistent-file.tar.gz error: true grep: "[Nn]o such file" - name: no such file error with no extension filenames: nonexistent-file error: true grep: "[Nn]o such file" - name: try to extract a directory error filenames: test-directory prerun: mkdir test-directory error: true grep: "cannot work with a directory" - name: permission denied error filenames: unreadable-file.tar.gz prerun: | touch unreadable-file.tar.gz chmod 000 unreadable-file.tar.gz cleanup: rm -f unreadable-file.tar.gz error: true grep: "[Pp]ermission denied" - name: permission denied no-pipe file error filenames: unreadable-file.zip prerun: | touch unreadable-file.zip chmod 000 unreadable-file.zip cleanup: rm -f unreadable-file.zip error: true grep: "[Pp]ermission denied" - name: bad file error filenames: bogus-file.tar.gz prerun: | touch bogus-file.tar.gz cleanup: rm -f bogus-file.tar.gz error: true grep: "returned status code [^0]" - name: try to extract in unwritable directory directory: unwritable-dir filenames: ../test-1.23.tar.gz prerun: chmod 500 . error: true grep: "cannot extract here: [Pp]ermission denied" - name: recursive listing is a no-op options: -rl filenames: test-recursive-badperms.tar.bz2 grep: test-badperms.tar antigrep: testdir/ - name: graceful coping when many extraction directories are taken directory: busydir prerun: | mkdir test-1.23 for i in $(seq 1 10); do mkdir test-1.23.$i; done filenames: ../test-1.23.tar.gz grep: "WARNING: extracting" - name: graceful coping when many decompression targets are taken directory: busydir prerun: | touch test-text for i in $(seq 1 10); do touch test-text.$i; done filenames: ../test-text.gz grep: "WARNING: extracting" - name: output filenames with -v options: -v -n filenames: test-onedir.tar.gz test-text.gz output: | test-onedir.tar.gz: test-onedir/ test-onedir/test/ test-onedir/test/foobar test-onedir/test/quux test-text.gz: test-text - name: output filenames with -v and -f options: -nvf directory: busydir filenames: ../test-onedir.tar.gz output: | test/ test/foobar test/quux - name: list recursive archives options: "" filenames: test-deep-recursion.tar input: | r l n grep: '^test-deep-recursion/subsubdir/test-text\.gz$' - name: partly failed extraction options: -n filenames: test-tar-with-node.tar.gz baseline: | mkdir test-tar-with-node cd test-tar-with-node tar -zxf ../$1 grep: Cannot mknod - name: flat extraction of one-file archive directory: inside-dir options: -f filenames: ../test-onefile.tar.gz baseline: tar -zxf $1 antigrep: "contains" - name: test recursive extraction of one archive directory: inside-dir options: "" filenames: ../test-one-archive.tar.gz baseline: | tar -zxf $1 zcat test-text.gz >test-text input: | h o - name: extracting empty archive filenames: test-empty.tar.bz2 options: "" baseline: "" antigrep: '.' - name: listing empty archive filenames: test-empty.tar.bz2 options: -l antigrep: '.' - name: download and extract filenames: http://brettcsmith.org/2007/dtrx/test-download.gz directory: inside-dir baseline: | wget "$1" zcat test-download.gz >test-download cleanup: rm -f test-download.gz test-download - name: recursive archive without prompt filenames: test-recursive-no-prompt.tar.bz2 options: "" baseline: | mkdir test-recursive-no-prompt cd test-recursive-no-prompt tar -jxf ../$1 antigrep: '.' dtrx-7.1/tests/test-text.gz0000600000175000017500000000002711662032265015023 0ustar brettbrett[B*Fzzodtrx-7.1/tests/test-text.lz0000600000175000017500000000004711662032265015032 0ustar brettbrettLZIP 4=P+zzo'dtrx-7.1/tests/test-text.xz0000600000175000017500000000007411662032265015046 0ustar brettbrett7zXZִF!t/hi {*XCO /}YZdtrx-7.1/tests/compare.py0000600000175000017500000002056311662032315014523 0ustar brettbrett#!/usr/bin/env python # -*- coding: utf-8 -*- # # compare.py -- High-level tests for dtrx. # Copyright © 2006-2009 Brett Smith . # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, see . import os import re import subprocess import yaml import sys import tempfile try: set except NameError: from sets import Set as set if os.path.exists('scripts/dtrx') and os.path.exists('tests'): os.chdir('tests') elif os.path.exists('../scripts/dtrx') and os.path.exists('../tests'): pass else: print "ERROR: Can't run tests in this directory!" sys.exit(2) DTRX_SCRIPT = os.path.realpath('../scripts/dtrx') SHELL_CMD = ['sh', '-se'] ROOT_DIR = os.path.realpath(os.curdir) OUTCOMES = ['error', 'failed', 'passed'] class ExtractorTestError(Exception): pass class ExtractorTest(object): def __init__(self, **kwargs): setattr(self, 'name', kwargs['name']) setattr(self, 'options', kwargs.get('options', '-n').split()) setattr(self, 'filenames', kwargs.get('filenames', '').split()) for key in ('directory', 'prerun', 'posttest', 'baseline', 'error', 'input', 'output', 'cleanup'): setattr(self, key, kwargs.get(key, None)) for key in ('grep', 'antigrep'): value = kwargs.get(key, []) if isinstance(value, str): value = [value] setattr(self, key, value) if self.input and (not self.input.endswith('\n')): self.input = self.input + '\n' def start_proc(self, command, stdin=None, output=None): process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=output, stderr=output) if stdin: process.stdin.write(stdin) process.stdin.close() return process def get_results(self, command, stdin=None): print >>self.outbuffer, "Output from %s:" % (' '.join(command),) self.outbuffer.flush() status = self.start_proc(command, stdin, self.outbuffer).wait() process = subprocess.Popen(['find'], stdout=subprocess.PIPE) output = process.stdout.read(-1) process.stdout.close() process.wait() return status, set(output.split('\n')) def run_script(self, key): commands = getattr(self, key) if commands is not None: if self.directory: directory_hint = '../' else: directory_hint = '' self.start_proc(SHELL_CMD + [directory_hint], commands) def get_shell_results(self): self.run_script('prerun') return self.get_results(SHELL_CMD + self.filenames, self.baseline) def get_extractor_results(self): self.run_script('prerun') return self.get_results([DTRX_SCRIPT] + self.options + self.filenames, self.input) def get_posttest_result(self): if not self.posttest: return 0 return self.start_proc(SHELL_CMD, self.posttest).wait() def clean(self): self.run_script('cleanup') if self.directory: target = os.path.join(ROOT_DIR, self.directory) extra_options = [] else: target = ROOT_DIR extra_options = ['(', '(', '-type', 'd', '!', '-name', 'CVS', '!', '-name', '.svn', ')', '-or', '-name', 'test-text', '-or', '-name', 'test-onefile', ')'] status = subprocess.call(['find', target, '-mindepth', '1', '-maxdepth', '1'] + extra_options + ['-exec', 'rm', '-rf', '{}', ';']) if status != 0: raise ExtractorTestError("cleanup exited with status code %s" % (status,)) def show_status(self, status, message=None): raw_status = status.lower() if raw_status != 'passed': self.outbuffer.seek(0, 0) sys.stdout.write(self.outbuffer.read(-1)) if message is None: last_part = '' else: last_part = ': %s' % (message,) print "%7s: %s%s" % (status, self.name, last_part) return raw_status def compare_results(self, actual): posttest_result = self.get_posttest_result() self.clean() status, expected = self.get_shell_results() self.clean() if expected != actual: print >>self.outbuffer, "Only in baseline results:" print >>self.outbuffer, '\n'.join(expected.difference(actual)) print >>self.outbuffer, "Only in actual results:" print >>self.outbuffer, '\n'.join(actual.difference(expected)) return self.show_status('FAILED') elif posttest_result != 0: print >>self.outbuffer, "Posttest gave status code", posttest_result return self.show_status('FAILED') return self.show_status('Passed') def have_error_mismatch(self, status): if self.error and (status == 0): return "dtrx did not return expected error" elif (not self.error) and (status != 0): return "dtrx returned error code %s" % (status,) return None def grep_output(self, output): for pattern in self.grep: if not re.search(pattern.replace(' ', '\\s+'), output, re.MULTILINE): return "output did not match %s" % (pattern) for pattern in self.antigrep: if re.search(pattern.replace(' ', '\\s+'), output, re.MULTILINE): return "output matched antigrep %s" % (self.antigrep) return None def check_output(self, output): if ((self.output is not None) and (self.output.strip() != output.strip())): return "output did not match provided text" return None def check_results(self): self.clean() status, actual = self.get_extractor_results() self.outbuffer.seek(0, 0) self.outbuffer.readline() output = self.outbuffer.read(-1) problem = (self.have_error_mismatch(status) or self.check_output(output) or self.grep_output(output)) if problem: return self.show_status('FAILED', problem) if self.baseline is not None: return self.compare_results(actual) else: self.clean() return self.show_status('Passed') def run(self): self.outbuffer = tempfile.TemporaryFile() if self.directory: os.mkdir(self.directory) os.chdir(self.directory) try: result = self.check_results() except ExtractorTestError, error: result = self.show_status('ERROR', error) self.outbuffer.close() if self.directory: os.chdir(ROOT_DIR) subprocess.call(['chmod', '-R', '700', self.directory]) subprocess.call(['rm', '-rf', self.directory]) return result test_db = open('tests.yml') test_data = yaml.load(test_db.read(-1)) test_db.close() tests = [ExtractorTest(**data) for data in test_data] for original_data in test_data: if (original_data.has_key('directory') or (not original_data.has_key('baseline'))): continue data = original_data.copy() data['name'] += ' in ..' data['directory'] = 'inside-dir' data['filenames'] = ' '.join(['../%s' % filename for filename in data.get('filenames', '').split()]) tests.append(ExtractorTest(**data)) results = [test.run() for test in tests] counts = {} for outcome in OUTCOMES: counts[outcome] = 0 for result in results: counts[result] += 1 print " Totals:", ', '.join(["%s %s" % (counts[key], key) for key in OUTCOMES]) dtrx-7.1/tests/test-deep-recursion.tar0000600000175000017500000002400011662032265017126 0ustar brettbrettsubdir/0000700000175000017500000000000010744557444011374 5ustar brettbrettsubdir/test-text.gz0000600000175000017500000000002710744557444013700 0ustar brettbrett[B*Fzzosubdir/subsubdir/0000700000175000017500000000000010744557446013400 5ustar brettbrettsubdir/subsubdir/test-text.gz0000600000175000017500000000002710744557446015704 0ustar brettbrett[B*Fzzodtrx-7.1/tests/test-1.23.gem0000600000175000017500000002400011662032265014547 0ustar brettbrettdata.tar.gz0000600000175000017500000000033410701163632012127 0ustar brettbrettհCEK0@. }@ݿMt@6mCt'UE(xD{"Yz[Z]ԎMOͼOTzj'О{7N@QrVY#syjtݼöWiakQ?`RW(metadata.gz0000600000175000017500000000002710701163632012210 0ustar brettbrett[B*Fzzodtrx-7.1/tests/test-1.23_all.deb0000600000175000017500000000113411662032265015364 0ustar brettbrett! debian-binary/ 1162088743 1000 1000 100644 4 ` 2.0 control.tar.gz/ 1162088743 1000 1000 100644 220 ` հCEK0@. }@ݿMt@6mCt'UE(xD{"Yz[Z]ԎMOͼOTzj'О{7N@QrVY#syjtݼöWiakQ?`RW(data.tar.gz/ 1162088781 1000 1000 100644 191 ` MDEA @Q :L9Mtۤ W4۰h!䆓*UT,H:[v9o{l}ާ?J0 ??IZ8xF <տ^^o1(eZ:"u]6vF_Xk@K( dtrx-7.1/tests/test-onedir.tar.bz20000600000175000017500000000024011662032265016156 0ustar brettbrettBZh91AY&SYY@s@@ *hƣ&QI!&#OSwocB\ ˫chaqe4C>q \0ː@QS̤ `Kvc^;7Zn2D?"(Hoˀdtrx-7.1/tests/test-1.23.7z0000600000175000017500000000026711662032265014350 0ustar brettbrett7z'v!D9${Sq058iIgsC5d:<މz숙颀?;DEkB(#ڀ7 ,Ee@h ,p=rF6 v #] ) ; @dtrx-7.1/tests/test-1.23.zip0000600000175000017500000000057411662032265014613 0ustar brettbrettPK t\51/2/3UT CE+CEUxPK t\5a/bUT CE+CEUxPK t\5foobarUT CE+CEUxPK t\5 1/2/3UTCEUxPK t\5 8a/bUTCEUxPK t\5 nfoobarUTCEUxPKdtrx-7.1/tests/test-1.23.lzh0000600000175000017500000000047211662032315014577 0ustar brettbrett-lhd-Es? UPAQ1T2N-lhd-Es? UPAQ12T2N-lh1-t\5 3UPQ12TCE-lhd-Es? UPAQaT2N2-lh1-t\5 bUPQaTCEJ-lh1-t\5 foobarUPQTCEdtrx-7.1/tests/test-onefile.tar.gz0000600000175000017500000000017511662032265016251 0ustar brettbrett`FGA @D^{=yV[ZԦ(/^VS}ILs4KM좨YnqaBa*~u·O l<(dtrx-7.1/PKG-INFO0000600000175000017500000000277611662032736012473 0ustar brettbrettMetadata-Version: 1.0 Name: dtrx Version: 7.1 Summary: Script to intelligently extract multiple archive types Home-page: http://www.brettcsmith.org/2007/dtrx/ Author: Brett Smith Author-email: brettcsmith@brettcsmith.org License: GNU General Public License, version 3 or later Download-URL: http://www.brettcsmith.org/2007/dtrx/ Description: dtrx extracts archives in a number of different formats; it currently supports tar, zip (including self-extracting .exe files), cpio, rpm, deb, gem, 7z, cab, rar, and InstallShield files. It can also decompress files compressed with gzip, bzip2, lzma, xz, or compress. In addition to providing one command to handle many different archive types, dtrx also aids the user by extracting contents consistently. By default, everything will be written to a dedicated directory that's named after the archive. dtrx will also change the permissions to ensure that the owner can read and write all those files. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Topic :: Utilities