diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/INSTALL b/INSTALL
index c3bb66b..b05b60c 100644
--- a/INSTALL
+++ b/INSTALL
@@ -46,7 +46,10 @@ InstallShield archives
   unshield
 
 rar archives
-  unrar
+  unar or unrar
+
+arj archives
+  arj
 
 Files compressed with gzip or compress
   zcat
@@ -60,6 +63,12 @@ Files compressed with lzma
 Files compressed with xz
   xzcat
 
+Files compressed with lrzip
+  lrzcat
+
+Files compressed with lzip
+  lzip
+
 Installation
 ------------
 
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..33826cb
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include COPYING INSTALL README NEWS
+recursive-include tests *
+exclude tests/testscript.sh
diff --git a/NEWS b/NEWS
index 92c81c0..9410bd4 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,38 @@
 Changes in dtrx
 ===============
 
+Version 7.2
+-----------
+
+Thanks to Ville Skyllä, who contributed most of the new features and
+enhancements in this release.
+
+New features
+~~~~~~~~~~~~
+
+ * dtrx now supports the arj archive, lrzip encoding, and several specific
+   file extensions.
+
+ * If unar is available, dtrx can try to use it to extract rar archives.
+
+Bug fixes
+~~~~~~~~~
+
+ * dtrx will get correct file magic information for archives it's reading
+   through symbolic links.
+
+ * File listings for rar archives now include full paths.
+
+Development changes
+~~~~~~~~~~~~~~~~~~~
+
+ * dtrx development is now `hosted on Gitorious`_.
+
+.. _hosted on Gitorious: http://gitorious.org/dtrx
+
+ * The test script can run specific tests, and has improved output on
+   interactive terminals.
+
 Version 7.1
 -----------
 
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index 685b86d..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,31 +0,0 @@
-Metadata-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
diff --git a/README b/README
index 1cc5097..9ec47b3 100644
--- a/README
+++ b/README
@@ -41,8 +41,8 @@ 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.
+gem, 7z, cab, rar, lzh, arj, and InstallShield files.  It can also decompress
+files compressed with gzip, bzip2, lzma, xz, lrzip, lzip, or compress.
 
 In addition to providing one command to handle many different archive
 types, dtrx also aids the user by extracting contents consistently.  By
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..82352dc
--- /dev/null
+++ b/TODO
@@ -0,0 +1,19 @@
+-*- text -*-
+
+To do:
+
+Things which I have a use case/anti-use case for:
+* Support pisi packages (http://paketler.pardus.org.tr/pardus-2007/)
+* Steal ideas from <http://martin.ankerl.com/files/e>.
+* More consistently raise and handle exceptions.
+
+Things that are generally good:
+* Better tests.
+* Better error messages.
+
+Things I think might be good but can't prove:
+* Use zipfile instead of the zip commands.
+* Processing from stdin.
+* shar support.
+* --expert mode: prompts don't show an explanation of what the options are,
+  unless you ask with ?.
diff --git a/contributions.mbox b/contributions.mbox
new file mode 100644
index 0000000..553841b
--- /dev/null
+++ b/contributions.mbox
@@ -0,0 +1,172 @@
+From Peter.Kelemen@gmail.com Fri Sep 19 18:51:00 2008
+Return-Path: <Peter.Kelemen@gmail.com>
+Date: Sat, 20 Sep 2008 00:51:17 +0200
+From: KELEMEN Peter <Peter.Kelemen@gmail.com>
+To: Brett Smith <brettcsmith@brettcsmith.org>
+Subject: [PATCH] dtrx: RAR support
+Message-ID: <20080919225117.GA993@kaylee>
+MIME-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+User-Agent: Mutt/1.5.17+20080114 (2008-01-14)
+Status: RO
+X-Status: A
+Content-Length: 1589
+Lines: 48
+
+Brett,
+
+The following patch adds RAR support for dtrx.
+
+HTH,
+Peter
+
+diff -r ececf7836546 scripts/dtrx
+--- a/scripts/dtrx	Tue Sep 16 21:51:59 2008 -0400
++++ b/scripts/dtrx	Sat Sep 20 00:50:16 2008 +0200
+@@ -515,6 +515,25 @@ class ShieldExtractor(NoPipeExtractor):
+         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
+@@ -775,6 +794,10 @@ class ExtractorBuilder(object):
+                              'mimetypes': ('x-cab',),
+                              'extensions': ('cab',),
+                              'magic': ('Microsoft Cabinet Archive',)},
++                     'rar': {'extractor': RarExtractor,
++                             'mimetypes': ('rar',),
++                             'extensions': ('rar'),
++                             'magic': ('RAR archive')},
+                      'shield': {'extractor': ShieldExtractor,
+                                 'mimetypes': ('x-cab',),
+                                 'extensions': ('cab', 'hdr'),
+
+
+From Peter.Kelemen@gmail.com Wed Sep 24 09:42:18 2008
+X-Original-To: me@brettcsmith.org
+Date: Wed, 24 Sep 2008 15:31:18 +0200
+From: KELEMEN Peter <Peter.Kelemen@gmail.com>
+To: Brett Smith <me@brettcsmith.org>
+Subject: Re: [PATCH] dtrx: RAR support
+Message-ID: <20080924133118.GG8943@kyra>
+References: <20080919225117.GA993@kaylee> <20080921180522.GB15446@canonical.org>
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Disposition: inline
+Content-Transfer-Encoding: 8bit
+In-Reply-To: <20080921180522.GB15446@canonical.org>
+User-Agent: Mutt/1.5.17+20080114 (2008-01-14)
+Status: RO
+Content-Length: 684
+Lines: 25
+
+* Brett Smith (me@brettcsmith.org) [20080921 14:05]:
+
+> Thanks very much for this; it looks great.
+
+You're welcome.
+
+> Can you please confirm for me that it's licensed under GPLv3 or
+> any later version?  A simple "yes" will do.  :)
+
+Sure, GPLv3 is OK.
+
+> Also, do you want me to add any copyright notice(s) for your
+> contribution?
+
+I'm ambivalent, I trust your judgement.  Should you choose to do
+so, please use my Peter.Kelemen@gmail.com address.
+
+Thanks,
+Peter
+
+-- 
+    .+'''+.         .+'''+.         .+'''+.         .+'''+.         .+''
+ Kelemen Péter     /       \       /       \     Peter.Kelemen@gmail.com
+.+'         `+...+'         `+...+'         `+...+'         `+...+'
+
+
+From - Thu Dec 01 18:53:16 2011
+Return-Path: <ville.skytta@iki.fi>
+X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on llewellyn
+X-Spam-Level: 
+X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,SPF_PASS
+	autolearn=ham version=3.3.1
+X-Original-To: me@brettcsmith.org
+Delivered-To: me@brettcsmith.org
+Received: from filtteri1.pp.htv.fi (filtteri1.pp.htv.fi [213.243.153.184])
+	by brettcsmith.org (Postfix) with ESMTP id A51BA67A6
+	for <me@brettcsmith.org>; Thu,  1 Dec 2011 02:22:59 -0500 (EST)
+Received: from localhost (localhost [127.0.0.1])
+	by filtteri1.pp.htv.fi (Postfix) with ESMTP id CC02018B3AA
+	for <me@brettcsmith.org>; Thu,  1 Dec 2011 09:22:58 +0200 (EET)
+X-Virus-Scanned: Debian amavisd-new at pp.htv.fi
+Received: from smtp5.welho.com ([213.243.153.39])
+	by localhost (filtteri1.pp.htv.fi [213.243.153.184]) (amavisd-new, port 10024)
+	with ESMTP id nX7Bob2-Ku0p for <me@brettcsmith.org>;
+	Thu,  1 Dec 2011 09:22:56 +0200 (EET)
+Received: from viper.bobcat.mine.nu (cs181085020.pp.htv.fi [82.181.85.20])
+	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+	(No client certificate requested)
+	by smtp5.welho.com (Postfix) with ESMTPS id 50F675BC002
+	for <me@brettcsmith.org>; Thu,  1 Dec 2011 09:22:56 +0200 (EET)
+Message-ID: <4ED72B50.60000@iki.fi>
+Date: Thu, 01 Dec 2011 09:22:56 +0200
+From: =?ISO-8859-1?Q?Ville_Skytt=E4?= <ville.skytta@iki.fi>
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.24) Gecko/20111108 Fedora/3.1.16-1.fc14 Thunderbird/3.1.16
+MIME-Version: 1.0
+To: Brett Smith <me@brettcsmith.org>
+Subject: Re: [PATCH 0 of 3] Some trivial dtrx patches
+References: <patchbomb.1322320204@viper.bobcat.mine.nu> <4ED459D2.4090703@brettcsmith.org> <4ED52917.900@iki.fi> <4ED5B081.4040505@brettcsmith.org> <4ED68074.70500@iki.fi> <4ED6C398.8050101@brettcsmith.org>
+In-Reply-To: <4ED6C398.8050101@brettcsmith.org>
+X-Enigmail-Version: 1.1.2
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: 8bit
+
+On 12/01/2011 02:00 AM, Brett Smith wrote:
+> On 11/30/2011 02:13 PM, Ville Skytt� wrote:
+>> BTW, you mentioned earlier that you were applying some of my initial
+>> trival patches but I don't see them in the public hg repo yet, did you
+>> find a problem with them?
+> 
+> No, no problem.  That's really weird -- they started showing up at 
+> <http://brettcsmith.org/2007/dtrx/dtrx/> as soon as I pushed them, and 
+> when I clone that repository with hg they show up in the log too.  How 
+> are you checking this?
+
+hg pull -u.  But I see the two first ones are indeed in the repository,
+so it's possible that I've managed to mess up something locally.  No
+problem, I'll probably just throw away my earlier clone.
+
+> I'm happy with the second round of patches too, but I meant to ask 
+> before I apply them: since they're more substantive, can I go ahead and 
+> add your copyright line to the source code?  And can you please just 
+> confirm that you're okay with this code being distributed under GPLv3 or 
+> later?
+
+Yes, go ahead, and GPLv3+ is fine.  (That means that I may need to
+switch from GPLv2+ to GPLv3+ for some other utilities I have that may be
+using dtrx in the future, but that shouldn't be a problem.)
+
+
diff --git a/debian/changelog b/debian/changelog
index db0b033..e3bf155 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,9 @@
-dtrx (7.1-3) UNRELEASED; urgency=medium
+dtrx (7.1+git20111204.671ccf7-1) UNRELEASED; urgency=medium
 
   * Bump debhelper from old 11 to 12.
+  * New upstream snapshot.
 
- -- Debian Janitor <janitor@jelmer.uk>  Wed, 16 Oct 2019 07:24:15 +0000
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 07 Dec 2019 07:35:29 +0000
 
 dtrx (7.1-2) unstable; urgency=medium
 
diff --git a/scripts/dtrx b/scripts/dtrx
index 2fc99e3..d004f98 100755
--- a/scripts/dtrx
+++ b/scripts/dtrx
@@ -4,6 +4,7 @@
 # dtrx -- Intelligently extract various archive types.
 # Copyright © 2006-2011 Brett Smith <brettcsmith@brettcsmith.org>
 # Copyright © 2008 Peter Kelemen <Peter.Kelemen@gmail.com>
+# Copyright © 2011 Ville Skyttä <ville.skytta@iki.fi>
 #
 # 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
@@ -49,6 +50,7 @@ VERSION = "7.1"
 VERSION_BANNER = """dtrx version %s
 Copyright © 2006-2011 Brett Smith <brettcsmith@brettcsmith.org>
 Copyright © 2008 Peter Kelemen <Peter.Kelemen@gmail.com>
+Copyright © 2011 Ville Skyttä <ville.skytta@iki.fi>
 
 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
@@ -83,6 +85,7 @@ 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.encodings_map.setdefault('.lrz', 'lrzip')
 mimetypes.types_map.setdefault('.gem', 'application/x-ruby-gem')
 
 logger = logging.getLogger('dtrx-log')
@@ -141,7 +144,8 @@ EXTRACTION_ERRORS = (ExtractorError, ExtractorUnusable, OSError, IOError)
 
 class BaseExtractor(object):
     decoders = {'bzip2': ['bzcat'], 'gzip': ['zcat'], 'compress': ['zcat'],
-                'lzma': ['lzcat'], 'xz': ['xzcat'], 'lzip': ['lzip', '-cd']}
+                'lzma': ['lzcat'], 'xz': ['xzcat'], 'lzip': ['lzip', '-cd'],
+                'lrzip': ['lrzcat', '-q'], 'lrz': ['lrzcat', '-q']}
     name_checker = DirectoryChecker
 
     def __init__(self, filename, encoding):
@@ -568,7 +572,6 @@ class CABExtractor(NoPipeExtractor):
     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):
@@ -608,11 +611,12 @@ class ShieldExtractor(NoPipeExtractor):
 class RarExtractor(NoPipeExtractor):
     file_type = 'RAR archive'
     extract_command = ['unrar', 'x']
-    list_command = ['unrar', 'l']
+    list_command = ['unrar', 'v']
     border_re = re.compile('^-+$')
 
     def get_filenames(self):
         inside = False
+        isfile = True
         for line in NoPipeExtractor.get_filenames(self):
             if self.border_re.match(line):
                 if inside:
@@ -620,7 +624,36 @@ class RarExtractor(NoPipeExtractor):
                 else:
                     inside = True
             elif inside:
-                yield line.split(' ')[1]
+                if isfile:
+                    yield line.strip()
+                isfile = not isfile
+        self.archive.close()
+
+
+class UnarchiverExtractor(NoPipeExtractor):
+    file_type = 'RAR archive'
+    extract_command = ['unar', '-D']
+    list_command = ['lsar']
+
+    def get_filenames(self):
+        output = NoPipeExtractor.get_filenames(self)
+        output.next()
+        for line in output:
+            end_index = line.rfind('(')
+            yield line[:end_index].strip()
+            
+
+class ArjExtractor(NoPipeExtractor):
+    file_type = 'ARJ archive'
+    extract_command = ['arj', 'x', '-y']
+    list_command = ['arj', 'v']
+    prefix_re = re.compile(r'^\d+\)\s+')
+
+    def get_filenames(self):
+        for line in NoPipeExtractor.get_filenames(self):
+            match = self.prefix_re.match(line)
+            if match:
+                yield line[match.end():]
         self.archive.close()
 
 
@@ -889,12 +922,12 @@ class ExtractorBuilder(object):
                              'magic': ('POSIX tar archive',)},
                      'zip': {'extractors': (ZipExtractor, SevenExtractor),
                              'mimetypes': ('zip',),
-                             'extensions': ('zip',),
+                             'extensions': ('zip', 'jar', 'epub', 'xpi'),
                              'magic': ('(Zip|ZIP self-extracting) archive',)},
                      'lzh': {'extractors': (LZHExtractor,),
                              'mimetypes': ('x-lzh', 'x-lzh-compressed'),
                              'extensions': ('lzh', 'lha'),
-                             'magic': ('LHa [\d\.\?]+ archive',)},
+                             'magic': (r'LHa [\d\.\?]+ archive',)},
                      'rpm': {'extractors': (RPMExtractor,),
                              'mimetypes': ('x-redhat-package-manager', 'x-rpm'),
                              'extensions': ('rpm',),
@@ -920,10 +953,14 @@ class ExtractorBuilder(object):
                              'mimetypes': ('x-cab',),
                              'extensions': ('cab',),
                              'magic': ('Microsoft Cabinet Archive',)},
-                     'rar': {'extractors': (RarExtractor,),
+                     'rar': {'extractors': (RarExtractor, UnarchiverExtractor),
                              'mimetypes': ('rar',),
                              'extensions': ('rar',),
                              'magic': ('RAR archive',)},
+                     'arj': {'extractors': (ArjExtractor,),
+                             'mimetypes': ('arj',),
+                             'extensions': ('arj',),
+                             'magic': ('ARJ archive',)},
                      'shield': {'extractors': (ShieldExtractor,),
                                 'mimetypes': ('x-cab',),
                                 'extensions': ('cab', 'hdr'),
@@ -951,13 +988,15 @@ class ExtractorBuilder(object):
     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', 'xz', 'tar.xz', 'txz'),
                     ('tar', 'lz', 'tar.lz'),
                     ('tar', 'compress', 'tar.Z', 'taz'),
+                    ('tar', 'lrz', 'tar.lrz'),
                     ('compress', 'gzip', 'Z', 'gz'),
                     ('compress', 'bzip2', 'bz2'),
                     ('compress', 'lzma', 'lzma'),
-                    ('compress', 'xz', 'xz')):
+                    ('compress', 'xz', 'xz'),
+                    ('compress', 'lrzip', 'lrz')):
         for extension in mapping[2:]:
             extension_map.setdefault(extension, []).append(mapping[:2])
 
@@ -966,6 +1005,7 @@ class ExtractorBuilder(object):
                     ('gzip', 'gzip compressed'),
                     ('lzma', 'LZMA compressed'),
                     ('lzip', 'lzip compressed'),
+                    ('lrzip', 'LRZIP compressed'),
                     ('xz', 'xz compressed')):
         for pattern in mapping[1:]:
             magic_encoding_map[re.compile(pattern)] = mapping[0]
@@ -1020,7 +1060,7 @@ class ExtractorBuilder(object):
     magic_map_matches = classmethod(magic_map_matches)
         
     def try_by_magic(cls, filename):
-        process = subprocess.Popen(['file', '-z', filename],
+        process = subprocess.Popen(['file', '-zL', filename],
                                    stdout=subprocess.PIPE)
         status = process.wait()
         if status != 0:
diff --git a/setup.py b/setup.py
index bda1d70..00d57a2 100644
--- a/setup.py
+++ b/setup.py
@@ -22,9 +22,9 @@ setup(name="dtrx",
                      '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.
+      .exe files), cpio, rpm, deb, gem, 7z, cab, rar, lzh, arj, and
+      InstallShield files.  It can also decompress files compressed with gzip,
+      bzip2, lzma, xz, lrzip, lzip, or compress.
 
       In addition to providing one command to handle many different archive
       types, dtrx also aids the user by extracting contents consistently.
diff --git a/tests/compare.py b/tests/compare.py
index 6a92fd1..f81e2b1 100644
--- a/tests/compare.py
+++ b/tests/compare.py
@@ -17,12 +17,15 @@
 # You should have received a copy of the GNU General Public License along
 # with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import fcntl
 import os
 import re
+import struct
 import subprocess
-import yaml
 import sys
 import tempfile
+import termios
+import yaml
 
 try:
     set
@@ -40,14 +43,44 @@ else:
 DTRX_SCRIPT = os.path.realpath('../scripts/dtrx')
 SHELL_CMD = ['sh', '-se']
 ROOT_DIR = os.path.realpath(os.curdir)
-OUTCOMES = ['error', 'failed', 'passed']
+NUM_TESTS = 0
 
 class ExtractorTestError(Exception):
     pass
 
 
+class StatusWriter(object):
+    def __init__(self):
+        try:
+            size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ,
+                               struct.pack("HHHH", 0, 0, 0, 0))
+        except IOError:
+            self.show = self.show_file
+        else:
+            self.width = struct.unpack("HHHH", size)[1] - 1
+            self.last_width = self.width
+            self.show = self.show_term
+
+    def show_term(self, message):
+        sys.stdout.write(message.ljust(self.last_width) + "\r")
+        sys.stdout.flush()
+        self.last_width = max(self.width, len(message))
+
+    def show_file(self, message):
+        if message:
+            print message
+
+    def clear(self):
+        self.show("")
+        
+
 class ExtractorTest(object):
+    status_writer = StatusWriter()
+
     def __init__(self, **kwargs):
+        global NUM_TESTS
+        NUM_TESTS += 1
+        self.test_num = NUM_TESTS
         setattr(self, 'name', kwargs['name'])
         setattr(self, 'options', kwargs.get('options', '-n').split())
         setattr(self, 'filenames', kwargs.get('filenames', '').split())
@@ -87,7 +120,7 @@ class ExtractorTest(object):
                 directory_hint = '../'
             else:
                 directory_hint = ''
-            self.start_proc(SHELL_CMD + [directory_hint], commands)
+            self.start_proc(SHELL_CMD + [directory_hint], commands).wait()
 
     def get_shell_results(self):
         self.run_script('prerun')
@@ -123,17 +156,21 @@ class ExtractorTest(object):
             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))
+    def show_pass(self):
+        self.status_writer.show("Passed %i/%i: %s" %
+                                (self.test_num, NUM_TESTS, self.name))
+        return 'passed'
+
+    def show_report(self, status, message=None):
+        self.status_writer.clear()
+        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
+        print "%s: %s%s\n" % (status, self.name, last_part)
+        return status.lower()
 
     def compare_results(self, actual):
         posttest_result = self.get_posttest_result()
@@ -145,11 +182,11 @@ class ExtractorTest(object):
             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')
+            return self.show_report('FAILED')
         elif posttest_result != 0:
             print >>self.outbuffer, "Posttest gave status code", posttest_result
-            return self.show_status('FAILED')
-        return self.show_status('Passed')
+            return self.show_report('FAILED')
+        return self.show_pass()
     
     def have_error_mismatch(self, status):
         if self.error and (status == 0):
@@ -183,12 +220,12 @@ class ExtractorTest(object):
         problem = (self.have_error_mismatch(status) or
                    self.check_output(output) or self.grep_output(output))
         if problem:
-            return self.show_status('FAILED', problem)
+            return self.show_report('FAILED', problem)
         if self.baseline is not None:
             return self.compare_results(actual)
         else:
             self.clean()
-            return self.show_status('Passed')
+            return self.show_pass()
 
     def run(self):
         self.outbuffer = tempfile.TemporaryFile()
@@ -198,7 +235,7 @@ class ExtractorTest(object):
         try:
             result = self.check_results()
         except ExtractorTestError, error:
-            result = self.show_status('ERROR', error)
+            result = self.show_report('ERROR', error)
         self.outbuffer.close()
         if self.directory:
             os.chdir(ROOT_DIR)
@@ -207,24 +244,48 @@ class ExtractorTest(object):
         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])
+class TestsRunner(object):
+    outcomes = ['error', 'failed', 'passed']
+
+    def __init__(self):
+        test_db = open('tests.yml')
+        self.test_data = yaml.load(test_db.read(-1))
+        test_db.close()
+        self.name_regexps = [re.compile(s) for s in sys.argv[1:]]
+        self.tests = [ExtractorTest(**data) for data in self.test_data
+                      if self.wanted_test(data)]
+        self.add_subdir_tests()
+
+    def wanted_test(self, data):
+        if not self.name_regexps:
+            return True
+        return filter(None, [r.search(data['name']) for r in self.name_regexps])
+
+    def add_subdir_tests(self):
+        for odata in self.test_data:
+            if ((not self.wanted_test(odata)) or odata.has_key('directory') or
+                (not odata.has_key('baseline'))):
+                continue
+            data = odata.copy()
+            data['name'] += ' in ..'
+            data['directory'] = 'inside-dir'
+            data['filenames'] = ' '.join(['../%s' % filename for filename in
+                                          data.get('filenames', '').split()])
+            self.tests.append(ExtractorTest(**data))
+
+    def run(self):
+        results = {}
+        for outcome in self.outcomes:
+            results[outcome] = 0
+        for test in self.tests:
+            results[test.run()] += 1
+        if self.tests:
+            self.tests[-1].status_writer.clear()
+        print "Totals:", ', '.join(["%s %s" % (results[key], key)
+                                    for key in self.outcomes])
+        return (results["error"] + results["failed"]) == 0
+
+
+runner = TestsRunner()
+if not runner.run():
+    sys.exit(1)
diff --git a/tests/test-1.23.arj b/tests/test-1.23.arj
new file mode 100644
index 0000000..3b01a22
Binary files /dev/null and b/tests/test-1.23.arj differ
diff --git a/tests/test-1.23.rar b/tests/test-1.23.rar
new file mode 100644
index 0000000..b12fa1e
Binary files /dev/null and b/tests/test-1.23.rar differ
diff --git a/tests/test-1.23.tar.lrz b/tests/test-1.23.tar.lrz
new file mode 100644
index 0000000..e24711b
Binary files /dev/null and b/tests/test-1.23.tar.lrz differ
diff --git a/tests/test-text.lrz b/tests/test-text.lrz
new file mode 100644
index 0000000..92b32d5
Binary files /dev/null and b/tests/test-text.lrz differ
diff --git a/tests/tests.yml b/tests/tests.yml
index 462baf3..82d5d78 100644
--- a/tests/tests.yml
+++ b/tests/tests.yml
@@ -1,3 +1,20 @@
+# tests.yml -- Whole-program comparison tests for dtrx
+# Copyright © 2006-2011 Brett Smith <brettcsmith@brettcsmith.org>
+# Copyright © 2011 Ville Skyttä <ville.skytta@iki.fi>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
 - name: basic .tar
   filenames: test-1.23.tar
   baseline: |
@@ -15,6 +32,11 @@
     cd test-1.23
     tar -jxf ../$1
 
+- name: basic .tar.lrz
+  filenames: test-1.23.tar.lrz
+  baseline: |
+    lrzcat $1 | tar -xf -
+
 - name: basic .zip
   filenames: test-1.23.zip
   baseline: |
@@ -66,6 +88,20 @@
     cpio -i --make-directories <$1
   antigrep: blocks?
 
+- name: basic .rar
+  filenames: test-1.23.rar
+  baseline: |
+    mkdir test-1.23
+    cd test-1.23
+    unar -D ../$1 || unrar x ../$1
+
+- name: basic .arj
+  filenames: test-1.23.arj
+  baseline: |
+    mkdir test-1.23
+    cd test-1.23
+    arj x -y ../$1
+
 - name: .deb metadata
   filenames: test-1.23_all.deb
   options: --metadata
@@ -129,6 +165,16 @@
   posttest: |
     exec [ "$(cat test-text)" = "hi" ]
 
+- name: decompressing lrzip, not interactive
+  directory: inside-dir
+  filenames: ../test-text.lrz
+  options: ""
+  antigrep: "."
+  baseline: |
+    lrzcat $1 >test-text
+  posttest: |
+    exec [ "$(cat test-text)" = "hi" ]
+
 - name: decompressing lzip, not interactive
   directory: inside-dir
   filenames: ../test-text.lz
@@ -257,6 +303,14 @@
     a/b
     foobar
 
+- name: list contents of .arj
+  options: -n -l
+  filenames: test-1.23.arj
+  output: |
+    a/b
+    1/2/3
+    foobar
+
 - name: list contents of .cpio
   options: -n -l
   filenames: test-1.23.cpio
diff --git a/web/common.css b/web/common.css
new file mode 100644
index 0000000..88c4f32
--- /dev/null
+++ b/web/common.css
@@ -0,0 +1,4 @@
+body { padding: 0 3em; }
+h1, h2, h3 { margin-left: -1em; }
+span.pname { font-family: monospace; }
+pre { margin-left: 2em; }
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..96d4986
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+
+<html><head><title>dtrx: Intelligent archive extraction</title>
+<link rel="stylesheet" href="common.css">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+<h1>dtrx: Intelligent archive extraction</h1>
+
+<h2>Introduction</h2>
+
+<p><span class="pname">dtrx</span> stands for &ldquo;Do The Right
+Extraction.&rdquo; It's a tool for Unix-like systems that takes all the
+hassle out of extracting archives.  Here's an example of how you use
+it:</p>
+
+<pre>$ dtrx linux-3.0.1.tar.bz2</pre>
+
+<p>That's basically the same thing as:</p>
+
+<pre>$ tar -jxf linux-3.0.1.tar.bz2</pre>
+
+<p>But there's more to it than that.  You know those really annoying files
+that don't put everything in a dedicated directory, and have the
+permissions all wrong?</p>
+
+<pre>$ tar -zvxf random-tarball.tar.gz
+foo
+bar
+data/
+data/text
+$ cd data/
+cd: permission denied: data</pre>
+
+<p><span class="pname">dtrx</span> takes care of all those problems for
+you, too:</p>
+
+<pre>$ dtrx random-tarball.tar.gz
+$ cd random-tarball/data
+$ cat text
+This all works properly.</pre>
+
+<p><span class="pname">dtrx</span> is simple and powerful.  Just use the
+same command for all your archive files, and they'll never frustrate you
+again.</p>
+
+<h2>Features</h2>
+
+<ul>
+
+<li><strong>Handles many archive types</strong>: You only need to remember
+one simple command to extract
+
+<span class="pname">tar</span>,
+<span class="pname">zip</span>,
+<span class="pname">cpio</span>,
+<span class="pname">deb</span>,
+<span class="pname">rpm</span>,
+<span class="pname">gem</span>,
+<span class="pname">7z</span>,
+<span class="pname">cab</span>,
+<span class="pname">lzh</span>,
+<span class="pname">rar</span>,
+<span class="pname">arj</span>,
+<span class="pname">gz</span>,
+<span class="pname">bz2</span>,
+<span class="pname">lzma</span>,
+<span class="pname">xz</span>,
+<span class="pname">lrzip</span>,
+<span class="pname">lzip</span>,
+and many kinds of
+<span class="pname">exe</span> files, including Microsoft Cabinet archives,
+InstallShield archives, and self-extracting <span class="pname">zip</span>
+files.
+
+If they have any extra compression, like <span class="pname">tar.bz2</span>
+files, <span class="pname">dtrx</span> will take care of that for you,
+too.</li>
+
+<li><strong>Keeps everything organized</strong>: <span
+class="pname">dtrx</span> will make sure that archives are extracted into
+their own dedicated directories.</li>
+
+<li><strong>Sane permissions</strong>: <span class="pname">dtrx</span> makes
+sure you can read and write all the files you just extracted, while leaving
+the rest of the permissions intact.</li>
+
+<li><strong>Recursive extraction</strong>: <span class="pname">dtrx</span> can
+find archives inside the archive and extract those too.</li>
+
+</ul>
+
+<h2>Download</h2>
+
+<p><a href="dtrx-7.1.tar.gz">Download <span class="pname">dtrx</span>
+7.1</a>.  The SHA1 checksum for this file
+is <tt>05cfe705a04a8b84571b0a5647cd2648720791a4</tt>.  Improvements in this
+release include:</p>
+
+<ul>
+
+<li>Support for LZH archives.</li>
+<li>Minor bug fixes in handling recursive extraction and empty
+  archives.</li>
+
+</ul>
+
+<p>If you would like to try the latest development version—or maybe do some
+work on it yourself—you can check out the
+project's <a href="http://git-scm.com/">Git</a>
+repository.  A
+<a href="http://gitorious.org/dtrx">web repository</a> is
+available, or you can just run:</p>
+
+<pre>$ git clone git://gitorious.org/dtrx/dtrx.git</pre>
+
+<h2>Requirements</h2>
+
+<p>If you have Python 2.4 or greater, this should work out of the box.  If
+you're stuck on Python 2.3, you can use this if you install
+the <a href="http://www.lysator.liu.se/~astrand/popen5/">subprocess
+module</a>.  You'll need the usual tools for the archive types you want to
+extract: for example, if you're extracting <span class="pname">zip</span>
+files, you'll need <span class="pname">zipinfo</span>
+and <span class="pname">unzip</span>.  See the <tt>INSTALL</tt> file included
+with <span class="pname">dtrx</span> for a complete list of necessary
+utilities.</p>
+
+<h2>Installation</h2>
+
+<p>You can just put <span class="pname">scripts/dtrx</span> wherever is
+convenient for you, but if you want to install the program system-wide, you
+can also run the following command as root or equivalent:</p>
+
+<pre>python setup.py install --prefix=/usr/local</pre>
+
+<p>See the included <tt>INSTALL</tt> file for more information.</p>
+
+</body>
+</html>