Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package dtrx for openSUSE:Factory checked in 
at 2026-09-14 16:20:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/dtrx (Old)
 and      /work/SRC/openSUSE:Factory/.dtrx.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "dtrx"

Mon Sep 14 16:20:24 2026 rev:4 rq:1377583 version:8.7.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/dtrx/dtrx.changes        2024-07-17 
15:16:06.060154707 +0200
+++ /work/SRC/openSUSE:Factory/.dtrx.new.1265/dtrx.changes      2026-09-14 
16:20:30.506844796 +0200
@@ -1,0 +2,19 @@
+Sat Sep 12 14:27:06 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 8.7.1:
+  * Add password options for 7z, zip, rar and arj archives
+  * Fix 7z extraction handling
+  * Require Python 3.10+, support 3.12 and 3.13, drop dead
+    Python 2 compat code
+  * Fix .tar.lz type mapping
+  * Magic test for archives with zero-length entry
+- Narrow %files glob to explicit module and dist-info entries
+- Port build to PEP 517 (%python3_pyproject_wheel/_install,
+  setuptools >= 75): upstream 8.7 dropped setup.py, %python3_build
+  no longer works
+- Add %check import smoke test
+- Strip stray shebang from the imported-only dtrx.py module
+- Repair 2008 changelog header rejected by check_dates_in_changes
+  (blank line after separator plus missing ' - ' before author)
+
+-------------------------------------------------------------------
@@ -84,2 +103 @@
-
-* Mon Oct 20 2008 Pascal Bleser <[email protected]> 6.3
+* Mon Oct 20 2008 - Pascal Bleser <[email protected]> 6.3

Old:
----
  dtrx-8.5.3.tar.gz

New:
----
  dtrx-8.7.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ dtrx.spec ++++++
--- /var/tmp/diff_new_pack.v5mO9i/_old  2026-09-14 16:20:31.392882096 +0200
+++ /var/tmp/diff_new_pack.v5mO9i/_new  2026-09-14 16:20:31.396882265 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package dtrx
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,7 +17,7 @@
 
 
 Name:           dtrx
-Version:        8.5.3
+Version:        8.7.1
 Release:        0
 Summary:        Intelligent Archive Extraction Tool
 License:        GPL-3.0-only
@@ -25,7 +25,9 @@
 Source:         
https://github.com/dtrx-py/dtrx/releases/download/%{version}/dtrx-%{version}.tar.gz
 BuildRequires:  python-rpm-macros
 BuildRequires:  python3-devel
-BuildRequires:  python3-setuptools
+BuildRequires:  python3-pip
+BuildRequires:  python3-setuptools >= 75
+BuildRequires:  python3-wheel
 Requires:       bzip2
 Requires:       cpio
 Requires:       gzip
@@ -52,16 +54,23 @@
 
 %prep
 %autosetup
+# dtrx.py is imported, never executed (entry point is %%{_bindir}/dtrx);
+# drop its stray shebang (rpmlint non-executable-script).
+sed -i '1{/^#!/d}' dtrx/dtrx.py
 
 %build
-%python3_build
+%python3_pyproject_wheel
 
 %install
-%python3_install
+%python3_pyproject_install
+
+%check
+PYTHONPATH=%{buildroot}%{python3_sitelib} python3 -B -c "import dtrx.dtrx"
 
 %files
 %license COPYING
 %doc README.md
 %{_bindir}/dtrx
-%{python3_sitelib}/*
+%{python3_sitelib}/dtrx
+%{python3_sitelib}/dtrx-%{version}.dist-info
 

++++++ dtrx-8.5.3.tar.gz -> dtrx-8.7.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/PKG-INFO new/dtrx-8.7.1/PKG-INFO
--- old/dtrx-8.5.3/PKG-INFO     2023-05-11 20:56:02.714164000 +0200
+++ new/dtrx-8.7.1/PKG-INFO     2025-12-01 19:23:07.367742500 +0100
@@ -1,28 +1,28 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: dtrx
-Version: 8.5.3
+Version: 8.7.1
 Summary: Script to intelligently extract multiple archive types
-Download-URL: https://github.com/dtrx-py/dtrx
-Author: Brett Smith
-Author-email: [email protected]
-License: GNU General Public License version 3 or later
-Project-URL: homepage, http://www.brettcsmith.org/2007/dtrx/
-Project-URL: code, https://github.com/dtrx-py/dtrx
+Author-email: Brett Smith <[email protected]>
+Maintainer-email: Brett Smith <[email protected]>
+License-Expression: GPL-3.0-or-later
+Project-URL: Homepage, http://www.brettcsmith.org/2007/dtrx/
+Project-URL: Repository, https://github.com/dtrx-py/dtrx
+Project-URL: Download, https://github.com/dtrx-py/dtrx
 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: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Utilities
-Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
+Requires-Python: >=3.10
 Description-Content-Type: text/markdown
 License-File: COPYING
+Requires-Dist: unsupported-python==1.0.0; platform_system == "Windows"
+Dynamic: license-file
 
 
[![GitHub](https://img.shields.io/badge/GitHub-dtrx--py/dtrx-8da0cb?style=for-the-badge&logo=github)](https://github.com/dtrx-py/dtrx)
 [![PyPI
@@ -38,10 +38,8 @@
   - [Development](#development)
     - [Contributions](#contributions)
     - [Issues](#issues)
+    - [Running Tests](#running-tests)
     - [Releases](#releases)
-    - [Invoke + Tests](#invoke--tests)
-    - [Linting](#linting)
-    - [Docker](#docker)
 
 <!-- tocstop -->
 
@@ -72,14 +70,6 @@
 20.04+, where the `dtrx` apt package was removed from the default ppas (likely
 due to being python2 only).
 
-I attempted to get the tests all working via `tox` , for which I used a
-Dockerfile to try to get some kind of environment consistency. You can run the
-tests by running (requires Docker installed):
-
-```bash
-./test.sh
-```
-
 ## Development
 
 ### Contributions
@@ -93,6 +83,19 @@
 example, the archive that failed to extract) or reproduction steps so we can
 address the problem quickly.
 
+### Running Tests
+
+The tests are most easily run from Docker. Check out the
+[`Dockerfile`](Dockerfile) for how that's set up.
+
+We push a pre-built image to Docker Hub as `dtrx-py/dtrx:latest`, so you can
+pull that image and run the tests like so:
+
+```bash
+docker run --rm -it --volume $(pwd):/workspace ghcr.io/dtrx-py/dtrx:latest \
+   bash -c "cp -r /workspace ~/scratch && cd ~/scratch && uv run -- 
tests/compare.py"
+```
+
 ### Releases
 
 Releases are tagged in this repo and published to pypi.org. The release process
@@ -101,18 +104,16 @@
 1. update the version specifier:
 
    ```bash
-   # update the VERSION value in dtrx/dtrx.py, then:
-   ❯ git add dtrx/dtrx.py
+   # update the version appropriately
+   ❯ uv version X.Y.Z
+   ❯ git add pyproject.toml uv.lock
    ❯ git commit  # fill in the commit message
    ```
 
-2. create an annotated tag for the release. usually good to put a list of new
-   commits since the previous tag, for example by listing them with:
+2. create an annotated tag for the release.
 
    ```bash
-   ❯ git log $(git describe --tags --abbrev=0)..HEAD --oneline
-   # create the annotated tag
-   ❯ git tag -a <version number>
+   ❯ git tag -a {,-m}X.Y.Z
    ```
 
    be sure to push the tag, `git push --tags`.
@@ -121,67 +122,3 @@
    PyPi
 
 See the [`Makefile`](Makefile) for details on what that rule does.
-
-### Invoke + Tests
-
-There's some minimal helper scripts for pyinvoke under [`tasks/`](tasks/).
-
-To bootstrap, run `pip install -r requirements.txt`, then `inv --list` to see
-available tasks:
-
-```bash
-❯ inv --list
-Available tasks:
-
-  build-docker                build docker image
-  push-docker                 push docker image
-  quick-test                  run quick tests in docker
-  rst2man                     run rst2man in docker
-  test-nonexistent-file-cmd   run test-nonexistent-file-cmd.sh
-  tox                         run tox in docker
-  windows                     just check that windows install fails. pulls a 
minimal wine docker image to test
-```
-
-To run the tests, run `inv tox`. Takes a couple of minutes to go through all 
the
-python versions.
-
-### Linting
-
-Linting is provided by [pre-commit](pre-commit.com). To use it, first install
-the pre-commit hook:
-
-```bash
-pip install pre-commit
-pre-commit install
-```
-
-pre-commit will run anytime `git commit` runs (disable with `--no-verify`). You
-can manually run it with `pre-commit run`.
-
-### Docker
-
-The tests in CI (and locally) can be run inside a Docker container, which
-provides all the tested python versions.
-
-This image is defined at [`Dockerfile`](Dockerfile). It's pushed to the GitHub
-Container Registry so it can be managed by the `dtrx-py` organization on 
GitHub-
-Docker Hub charges for Organizations.
-
-There are Invoke tasks for building + pushing the Docker image, which push both
-a `:latest` tag as well as a `:2022-09-16` ISO8601 numbered tag. The tag can
-then be updated in the GitHub actions runner.
-
-> Note: there's a bit of complexity around how the image is used, because the
-> dtrx tests need to run as a non-root user (there's one test that checks for
-> error handling when the output directory is not accessible by the current
-> user). To deal with this, there's an entrypoint script that switches user to 
a
-> non-root user, but that still has read/write access to the mounted host 
volume
-> (which is the cwd, intended for local development work). This is required on
-> Linux, where it's nice to have the host+container UID+GUID matching, so any
-> changes to the mounted host volume have the same permissions set.
->
-> In the GitHub actions runner, we need to run inside the same container (to
-> have access to the correct python versions for testing), and the github 
action
-> for checkout assumes it can write to somewhat arbitrary locations in the file
-> system (basically root access). So we switch to the non-root user _after_
-> checkout.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/README.md new/dtrx-8.7.1/README.md
--- old/dtrx-8.5.3/README.md    2023-04-28 22:50:11.000000000 +0200
+++ new/dtrx-8.7.1/README.md    2025-11-03 21:22:47.000000000 +0100
@@ -12,10 +12,8 @@
   - [Development](#development)
     - [Contributions](#contributions)
     - [Issues](#issues)
+    - [Running Tests](#running-tests)
     - [Releases](#releases)
-    - [Invoke + Tests](#invoke--tests)
-    - [Linting](#linting)
-    - [Docker](#docker)
 
 <!-- tocstop -->
 
@@ -46,14 +44,6 @@
 20.04+, where the `dtrx` apt package was removed from the default ppas (likely
 due to being python2 only).
 
-I attempted to get the tests all working via `tox` , for which I used a
-Dockerfile to try to get some kind of environment consistency. You can run the
-tests by running (requires Docker installed):
-
-```bash
-./test.sh
-```
-
 ## Development
 
 ### Contributions
@@ -67,6 +57,19 @@
 example, the archive that failed to extract) or reproduction steps so we can
 address the problem quickly.
 
+### Running Tests
+
+The tests are most easily run from Docker. Check out the
+[`Dockerfile`](Dockerfile) for how that's set up.
+
+We push a pre-built image to Docker Hub as `dtrx-py/dtrx:latest`, so you can
+pull that image and run the tests like so:
+
+```bash
+docker run --rm -it --volume $(pwd):/workspace ghcr.io/dtrx-py/dtrx:latest \
+   bash -c "cp -r /workspace ~/scratch && cd ~/scratch && uv run -- 
tests/compare.py"
+```
+
 ### Releases
 
 Releases are tagged in this repo and published to pypi.org. The release process
@@ -75,18 +78,16 @@
 1. update the version specifier:
 
    ```bash
-   # update the VERSION value in dtrx/dtrx.py, then:
-   ❯ git add dtrx/dtrx.py
+   # update the version appropriately
+   ❯ uv version X.Y.Z
+   ❯ git add pyproject.toml uv.lock
    ❯ git commit  # fill in the commit message
    ```
 
-2. create an annotated tag for the release. usually good to put a list of new
-   commits since the previous tag, for example by listing them with:
+2. create an annotated tag for the release.
 
    ```bash
-   ❯ git log $(git describe --tags --abbrev=0)..HEAD --oneline
-   # create the annotated tag
-   ❯ git tag -a <version number>
+   ❯ git tag -a {,-m}X.Y.Z
    ```
 
    be sure to push the tag, `git push --tags`.
@@ -95,67 +96,3 @@
    PyPi
 
 See the [`Makefile`](Makefile) for details on what that rule does.
-
-### Invoke + Tests
-
-There's some minimal helper scripts for pyinvoke under [`tasks/`](tasks/).
-
-To bootstrap, run `pip install -r requirements.txt`, then `inv --list` to see
-available tasks:
-
-```bash
-❯ inv --list
-Available tasks:
-
-  build-docker                build docker image
-  push-docker                 push docker image
-  quick-test                  run quick tests in docker
-  rst2man                     run rst2man in docker
-  test-nonexistent-file-cmd   run test-nonexistent-file-cmd.sh
-  tox                         run tox in docker
-  windows                     just check that windows install fails. pulls a 
minimal wine docker image to test
-```
-
-To run the tests, run `inv tox`. Takes a couple of minutes to go through all 
the
-python versions.
-
-### Linting
-
-Linting is provided by [pre-commit](pre-commit.com). To use it, first install
-the pre-commit hook:
-
-```bash
-pip install pre-commit
-pre-commit install
-```
-
-pre-commit will run anytime `git commit` runs (disable with `--no-verify`). You
-can manually run it with `pre-commit run`.
-
-### Docker
-
-The tests in CI (and locally) can be run inside a Docker container, which
-provides all the tested python versions.
-
-This image is defined at [`Dockerfile`](Dockerfile). It's pushed to the GitHub
-Container Registry so it can be managed by the `dtrx-py` organization on 
GitHub-
-Docker Hub charges for Organizations.
-
-There are Invoke tasks for building + pushing the Docker image, which push both
-a `:latest` tag as well as a `:2022-09-16` ISO8601 numbered tag. The tag can
-then be updated in the GitHub actions runner.
-
-> Note: there's a bit of complexity around how the image is used, because the
-> dtrx tests need to run as a non-root user (there's one test that checks for
-> error handling when the output directory is not accessible by the current
-> user). To deal with this, there's an entrypoint script that switches user to 
a
-> non-root user, but that still has read/write access to the mounted host 
volume
-> (which is the cwd, intended for local development work). This is required on
-> Linux, where it's nice to have the host+container UID+GUID matching, so any
-> changes to the mounted host volume have the same permissions set.
->
-> In the GitHub actions runner, we need to run inside the same container (to
-> have access to the correct python versions for testing), and the github 
action
-> for checkout assumes it can write to somewhat arbitrary locations in the file
-> system (basically root access). So we switch to the non-root user _after_
-> checkout.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/dtrx/dtrx.py new/dtrx-8.7.1/dtrx/dtrx.py
--- old/dtrx-8.5.3/dtrx/dtrx.py 2023-05-11 20:55:13.000000000 +0200
+++ new/dtrx-8.7.1/dtrx/dtrx.py 2025-12-01 19:20:55.000000000 +0100
@@ -19,12 +19,11 @@
 # You should have received a copy of the GNU General Public License along
 # with this program; if not, see <http://www.gnu.org/licenses/>.
 
-# Python 2.3 string methods: 'rfind', 'rindex', 'rjust', 'rstrip'
-
 from __future__ import absolute_import, print_function
 
 import errno
 import fcntl
+import importlib.metadata
 import itertools
 import logging
 import mimetypes
@@ -34,40 +33,25 @@
 import shutil
 import signal
 import stat
-import string
 import struct
+import subprocess
 import sys
 import tempfile
 import termios
 import textwrap
 import traceback
+import urllib.parse as urlparse
 from functools import cmp_to_key, total_ordering
 
-# Python 3 compatibility hacks commence
-try:
-    import urlparse
-except ImportError:
-    import urllib.parse as urlparse
-try:
-    import subprocess32 as subprocess
-except ImportError:
-    import subprocess
-
-if sys.version_info[0] >= 3:
-    get_input = input
 
-    def cmp(a, b):
-        return (a > b) - (a < b)
+def cmp(a, b):
+    return (a > b) - (a < b)
 
-else:
-    get_input = raw_input  # noqa: F821
 
 try:
-    set
-except NameError:
-    from sets import Set as set
-
-VERSION = "8.5.3"
+    VERSION = importlib.metadata.version("dtrx")
+except importlib.metadata.PackageNotFoundError:
+    VERSION = "DEVELOPMENT"
 VERSION_BANNER = """dtrx version %s
 Copyright © 2006-2011 Brett Smith <[email protected]>
 Copyright © 2008 Peter Kelemen <[email protected]>
@@ -83,11 +67,6 @@
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 Public License for more details.""" % (VERSION,)
 
-# Python3.6 optparse has a hard time parsing this, so ascii transform it
-if sys.version_info[:2] == (3, 6):
-    VERSION_BANNER = VERSION_BANNER.encode("ascii", 
errors="ignore").decode("ascii")
-
-
 MATCHING_DIRECTORY = 1
 ONE_ENTRY_KNOWN = 2
 BOMB = 3
@@ -157,7 +136,10 @@
     free_close = None
 
     def create(self):
-        return tempfile.mkdtemp(prefix=self.original_name + ".", dir=".")
+        dirname = tempfile.mkdtemp(prefix=self.original_name + ".", dir=".")
+        # We want to directory to be relative to current directory
+        dirname = os.path.join(".", os.path.relpath(dirname))
+        return dirname
 
 
 class NonblockingRead(object):
@@ -170,27 +152,9 @@
         flags = fcntl.fcntl(fd, fcntl.F_GETFL)
         fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
 
-    def python_2_readlines(self):
-        # XXX: There seems to be a bug in Python 2 where readline() returns
-        # "IOError: [Errno 11] Resource temporarily unavailable" on a
-        # non-blocking read from a pipe where the output lacks a newline.
-        # It doesn't happen in Python 3, so this hack can be deleted once we
-        # no longer care about Python 2.
-        out = ""
-        try:
-            while True:
-                # read a single byte at a time until we hit IOError
-                out += self.iostream.read(1).decode("ascii", "ignore")
-        except IOError:
-            pass
-        return out.splitlines(True)
-
     def readlines(self):
-        if sys.version_info[0] >= 3:
-            out = self.iostream.readlines()
-            return [line.decode("ascii", "ignore") for line in out]
-        else:
-            return self.python_2_readlines()
+        out = self.iostream.readlines()
+        return [line.decode("ascii", "ignore") for line in out]
 
 
 class ExtractorError(Exception):
@@ -222,9 +186,7 @@
         # option based on what's supported, since this behavior changed
         if encoding in ("lrzip", "lrz"):
             # need to check if this version of lrzip supports the -Q option
-            output = subprocess.check_output(
-                "lrzip --help", stderr=subprocess.STDOUT, shell=True
-            )
+            output = subprocess.check_output("lrzip --help", 
stderr=subprocess.STDOUT, shell=True)
             if b"-Q" in output:
                 decoder = ["lrzcat", "-Q"]
             else:
@@ -237,6 +199,7 @@
         self.filename = os.path.realpath(filename)
         self.encoding = encoding
         self.ignore_pw = False
+        self.password = None
         self.file_count = 0
         self.included_archives = []
         self.target = None
@@ -262,9 +225,7 @@
         try:
             logger.debug("running command: {}".format(command))
             processes.append(
-                subprocess.Popen(
-                    command, stdin=stdin, stdout=stdout, stderr=subprocess.PIPE
-                )
+                subprocess.Popen(command, stdin=stdin, stdout=stdout, 
stderr=subprocess.PIPE)
             )
         except OSError as error:
             if error.errno == errno.ENOENT:
@@ -279,10 +240,9 @@
             try:
                 return pipe.wait(timeout=1)
             except subprocess.TimeoutExpired:
-                logging.debug("timeout hit..")
+                logging.debug("timeout hit...")
                 self.timeout_check(pipe)
-                # Verify that we're not trying to extract password-protected
-                # archives in non-interactive mode
+                # Verify that we're not waiting for a password in 
non-interactive mode
                 if self.pw_prompted and self.ignore_pw:
                     pipe.kill()
                     # Whatever extractor we're using probably left the
@@ -292,16 +252,14 @@
                     self.stderr = ""
                     raise ExtractorError(
                         "cannot extract encrypted archive '%s' in 
non-interactive mode"
-                        % (self.filename)
+                        " without a password" % (self.filename)
                     )
 
     def send_stdout_to_dev_null(self):
         return True
 
     def run_pipes(self, final_stdout=None):
-        has_output_target = (
-            True if final_stdout or self.send_stdout_to_dev_null() else False
-        )
+        has_output_target = True if final_stdout or 
self.send_stdout_to_dev_null() else False
         if not self.pipes:
             return
         elif final_stdout is None:
@@ -342,9 +300,9 @@
             self.file_count += len(filenames)
             path = path[start_index:]
             for filename in filenames:
-                if ExtractorBuilder.try_by_mimetype(
+                if ExtractorBuilder.try_by_mimetype(filename) or 
ExtractorBuilder.try_by_extension(
                     filename
-                ) or ExtractorBuilder.try_by_extension(filename):
+                ):
                     self.included_archives.append(os.path.join(path, filename))
 
     def check_contents(self):
@@ -400,12 +358,8 @@
 
     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)
-        ):
+        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])
             self.pw_prompt = False  # Don't silently fail with wrong password
             raise ExtractorError(
@@ -417,10 +371,15 @@
         self.pipe(self.extract_pipe)
         self.run_pipes()
 
-    def extract(self, ignore_passwd=False):
+    def extract(self, ignore_passwd=False, password=None):
         self.ignore_pw = ignore_passwd
+        self.password = password
         try:
-            self.target = tempfile.mkdtemp(prefix=".dtrx-", dir=".")
+            dirname = tempfile.mkdtemp(prefix=".dtrx-", dir=".")
+            # We want to directory to be relative to current directory
+            dirname = os.path.join(".", os.path.relpath(dirname))
+            self.target = dirname
+
         except (OSError, IOError) as error:
             raise ExtractorError("cannot extract here: %s" % (error.strerror,))
         old_path = os.path.realpath(os.curdir)
@@ -478,14 +437,13 @@
         # 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)
-        ]:
+        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, ignore_passwd=False):
+    def extract(self, ignore_passwd=False, password=None):
         self.ignore_pw = ignore_passwd
+        self.password = password
         self.content_type = ONE_ENTRY_KNOWN
         self.content_name = self.basename()
         self.contents = None
@@ -564,9 +522,7 @@
         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(["ar", "p", self.filename, data_filename], "extracting 
data.tar from .deb")
         self.pipe(self.decoders[encoding], "decoding data.tar")
 
     def basename(self):
@@ -585,9 +541,7 @@
 
 class DebMetadataExtractor(DebExtractor):
     def prepare(self):
-        self.pipe(
-            ["ar", "p", self.filename, "control.tar.gz"], "control.tar.gz 
extraction"
-        )
+        self.pipe(["ar", "p", self.filename, "control.tar.gz"], 
"control.tar.gz extraction")
         self.pipe(["zcat"], "control.tar.gz decompression")
 
 
@@ -637,9 +591,7 @@
         extract_fmt_args = {
             "OUTPUT_FILE": 
os.path.splitext(os.path.basename(self.filename))[0],
         }
-        formatted_extract_commands = [
-            x.format(**extract_fmt_args) for x in self.extract_command
-        ]
+        formatted_extract_commands = [x.format(**extract_fmt_args) for x in 
self.extract_command]
 
         self.extract_pipe = formatted_extract_commands + [self.filename]
         BaseExtractor.extract_archive(self)
@@ -651,9 +603,18 @@
 
 class ZipExtractor(NoPipeExtractor):
     file_type = "Zip file"
-    extract_command = ["unzip", "-q"]
     list_command = ["zipinfo", "-1"]
 
+    @property
+    def extract_command(self):
+        """
+        Returns the extraction command and adds a password if given.
+        """
+        cmd = ["unzip", "-q"]
+        if self.password:
+            cmd.append("-P %s" % (self.password,))
+        return cmd
+
     def is_fatal_error(self, status):
         return (status or 0) > 1
 
@@ -702,20 +663,25 @@
 
 class SevenExtractor(NoPipeExtractor):
     file_type = "7z file"
-    extract_command = ["7z", "x"]
-    list_command = ["7z", "l"]
+    list_command = ["7z", "l", "-ba"]
     border_re = re.compile("^[- ]+$")
+    space_re = re.compile(" ")
+
+    @property
+    def extract_command(self):
+        """
+        Returns the extraction command and adds a password if given.
+        """
+        cmd = ["7z", "x"]
+        if self.password:
+            cmd.append("-p%s" % (self.password,))
+        return cmd
 
     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:]
+            if " " in line:
+                pos = line.rindex(" ") + 1
+                yield line[pos:]
         self.archive.close()
 
     def send_stdout_to_dev_null(self):
@@ -747,7 +713,7 @@
                 if fn_index is not None:
                     break
                 else:
-                    fn_index = string.rindex(line, " ") + 1
+                    fn_index = line.rindex(" ") + 1
             elif fn_index is not None:
                 yield line[fn_index:]
         self.archive.close()
@@ -809,10 +775,19 @@
 
 class RarExtractor(NoPipeExtractor):
     file_type = "RAR archive"
-    extract_command = ["unrar", "x"]
     list_command = ["unrar", "v"]
     border_re = re.compile("^-+$")
 
+    @property
+    def extract_command(self):
+        """
+        Returns the extraction command and adds a password if given.
+        """
+        cmd = ["unrar", "x"]
+        if self.password:
+            cmd.append("-p%s" % (self.password,))
+        return cmd
+
     def get_filenames(self):
         inside = False
         isfile = True
@@ -843,9 +818,18 @@
 
 class UnarchiverExtractor(NoPipeExtractor):
     file_type = "RAR archive"
-    extract_command = ["unar", "-D"]
     list_command = ["lsar"]
 
+    @property
+    def extract_command(self):
+        """
+        Returns the extraction command and adds a password if given.
+        """
+        cmd = ["unar", "-D"]
+        if self.password:
+            cmd.append("-p %s" % (self.password,))
+        return cmd
+
     def get_filenames(self):
         output = NoPipeExtractor.get_filenames(self)
         next(output)
@@ -856,10 +840,19 @@
 
 class ArjExtractor(NoPipeExtractor):
     file_type = "ARJ archive"
-    extract_command = ["arj", "x", "-y"]
     list_command = ["arj", "v"]
     prefix_re = re.compile(r"^\d+\)\s+")
 
+    @property
+    def extract_command(self):
+        """
+        Returns the extraction command and adds a password if given.
+        """
+        cmd = ["arj", "x", "-y"]
+        if self.password:
+            cmd.append("-g%s" % (self.password,))
+        return cmd
+
     def get_filenames(self):
         for line in NoPipeExtractor.get_filenames(self):
             match = self.prefix_re.match(line)
@@ -876,19 +869,17 @@
 
     def handle(self):
         command = "find"
-        status = subprocess.call(
-            [
-                "find",
-                self.extractor.target,
-                "-type",
-                "d",
-                "-exec",
-                "chmod",
-                "u+rwx",
-                "{}",
-                ";",
-            ]
-        )
+        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])
@@ -899,9 +890,7 @@
     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)
-            )
+            logger.warning("extracting %s to %s" % (self.extractor.filename, 
self.target))
 
 
 # The "where to extract" table, with options and archive types.
@@ -932,9 +921,7 @@
             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.rename(os.path.join(curdir, filename), os.path.join(newdir, 
filename))
             os.rmdir(curdir)
 
 
@@ -960,9 +947,7 @@
         )
 
     def organize(self):
-        source = os.path.join(
-            self.extractor.target, os.listdir(self.extractor.target)[0]
-        )
+        source = os.path.join(self.extractor.target, 
os.listdir(self.extractor.target)[0])
         if os.path.isdir(source):
             checker = DirectoryChecker
         else:
@@ -1008,9 +993,7 @@
 @total_ordering
 class BasePolicy(object):
     try:
-        size = fcntl.ioctl(
-            sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 
0, 0)
-        )
+        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
@@ -1036,7 +1019,7 @@
         while True:
             print("\n".join(question))
             try:
-                answer = get_input(self.prompt)
+                answer = input(self.prompt)
             except EOFError:
                 return self.answers[""]
             try:
@@ -1104,9 +1087,7 @@
         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.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):
@@ -1140,9 +1121,7 @@
 
     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
-        ):
+        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(
@@ -1162,12 +1141,10 @@
                 break
             print(
                 "\n%s\n"
-                % "\n".join(
-                    [
-                        os.path.join(target, included_root, filename)
-                        for filename in extractor.included_archives
-                    ]
-                )
+                % "\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
@@ -1300,7 +1277,7 @@
         ("tar", "gzip", "tar.gz", "tgz"),
         ("tar", "lzma", "tar.lzma", "tlz"),
         ("tar", "xz", "tar.xz", "txz"),
-        ("tar", "lz", "tar.lz"),
+        ("tar", "lzip", "tar.lz"),
         ("tar", "compress", "tar.Z", "taz"),
         ("tar", "lrz", "tar.lrz"),
         ("tar", "zstd", "tar.zst"),
@@ -1376,17 +1353,28 @@
 
     def try_by_magic(self, filename):
         try:
-            process = subprocess.Popen(
-                ["file", "-zL", filename], stdout=subprocess.PIPE
+            result = subprocess.run(
+                ["file", "-zL", filename], stdout=subprocess.PIPE, 
stderr=subprocess.PIPE, text=True
             )
-            status = process.wait()
-            if status != 0:
+            if result.returncode != 0:
                 return []
+            # if output contains 'ERROR:[', there was an error unzipping the
+            # first archive entry. re-run without -z.
+            output = result.stdout.split("\n")[0]
+            if "ERROR:[" in output:
+                result = subprocess.run(
+                    ["file", "-L", filename],
+                    stdout=subprocess.PIPE,
+                    stderr=subprocess.PIPE,
+                    text=True,
+                )
+                if result.returncode != 0:
+                    return []
+                output = result.stdout.split("\n")[0]
+
         except FileNotFoundError:
             logger.error("'file' command not found, skipping magic test")
             return []
-        output = process.stdout.readline().decode("ascii")
-        process.stdout.close()
         if output.startswith("%s: " % filename):
             output = output[len(filename) + 2 :]
         mimes = self.magic_map_matches(output, self.magic_mime_map)
@@ -1473,16 +1461,16 @@
                 print("%s/" % (filename,))
                 new_filenames = os.listdir(filename)
                 new_filenames = sorted(new_filenames, key=cmp_to_key(reverser))
-                filenames.extend(
-                    [pathjoin(filename, new_filename) for new_filename in 
new_filenames]
-                )
+                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, self.options.batch)
+            self.report(extractor.extract, self.options.batch, 
self.options.password)
             or self.report(self.get_handler, extractor)
             or self.report(self.current_handler.handle)
             or self.report(self.show_extraction, extractor)
@@ -1559,13 +1547,11 @@
         # get the lists of built-in extensions and combine them
         ext_map_base = set(ExtractorBuilder.extension_map.keys())
         ext_map = set(
-            itertools.chain(
-                *[
-                    x["extensions"]
-                    for x in ExtractorBuilder.extractor_map.values()
-                    if "extensions" in x
-                ]
-            )
+            itertools.chain(*[
+                x["extensions"]
+                for x in ExtractorBuilder.extractor_map.values()
+                if "extensions" in x
+            ])
         )
         ext_map = ext_map_base.union(ext_map)
 
@@ -1619,10 +1605,7 @@
             "--one-entry",
             dest="one_entry_default",
             default=None,
-            help=(
-                "specify extraction policy for one-entry "
-                + "archives: inside/rename/here"
-            ),
+            help=("specify extraction policy for one-entry " + "archives: 
inside/rename/here"),
         )
         parser.add_option(
             "-n",
@@ -1633,6 +1616,13 @@
             help="don't ask how to handle special cases",
         )
         parser.add_option(
+            "-p",
+            "--password",
+            dest="password",
+            default=None,
+            help="provide a password for password-protected archives",
+        )
+        parser.add_option(
             "-o",
             "--overwrite",
             dest="overwrite",
@@ -1737,9 +1727,7 @@
             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.stderr)
-                )
+                errors.append((extractor.file_type, extractor.encoding, error, 
extractor.stderr))
                 if extractor.target is not None:
                     self.clean_destination(extractor.target)
             else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/dtrx.egg-info/PKG-INFO 
new/dtrx-8.7.1/dtrx.egg-info/PKG-INFO
--- old/dtrx-8.5.3/dtrx.egg-info/PKG-INFO       2023-05-11 20:56:02.000000000 
+0200
+++ new/dtrx-8.7.1/dtrx.egg-info/PKG-INFO       2025-12-01 19:23:07.000000000 
+0100
@@ -1,28 +1,28 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: dtrx
-Version: 8.5.3
+Version: 8.7.1
 Summary: Script to intelligently extract multiple archive types
-Download-URL: https://github.com/dtrx-py/dtrx
-Author: Brett Smith
-Author-email: [email protected]
-License: GNU General Public License version 3 or later
-Project-URL: homepage, http://www.brettcsmith.org/2007/dtrx/
-Project-URL: code, https://github.com/dtrx-py/dtrx
+Author-email: Brett Smith <[email protected]>
+Maintainer-email: Brett Smith <[email protected]>
+License-Expression: GPL-3.0-or-later
+Project-URL: Homepage, http://www.brettcsmith.org/2007/dtrx/
+Project-URL: Repository, https://github.com/dtrx-py/dtrx
+Project-URL: Download, https://github.com/dtrx-py/dtrx
 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: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Utilities
-Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
+Requires-Python: >=3.10
 Description-Content-Type: text/markdown
 License-File: COPYING
+Requires-Dist: unsupported-python==1.0.0; platform_system == "Windows"
+Dynamic: license-file
 
 
[![GitHub](https://img.shields.io/badge/GitHub-dtrx--py/dtrx-8da0cb?style=for-the-badge&logo=github)](https://github.com/dtrx-py/dtrx)
 [![PyPI
@@ -38,10 +38,8 @@
   - [Development](#development)
     - [Contributions](#contributions)
     - [Issues](#issues)
+    - [Running Tests](#running-tests)
     - [Releases](#releases)
-    - [Invoke + Tests](#invoke--tests)
-    - [Linting](#linting)
-    - [Docker](#docker)
 
 <!-- tocstop -->
 
@@ -72,14 +70,6 @@
 20.04+, where the `dtrx` apt package was removed from the default ppas (likely
 due to being python2 only).
 
-I attempted to get the tests all working via `tox` , for which I used a
-Dockerfile to try to get some kind of environment consistency. You can run the
-tests by running (requires Docker installed):
-
-```bash
-./test.sh
-```
-
 ## Development
 
 ### Contributions
@@ -93,6 +83,19 @@
 example, the archive that failed to extract) or reproduction steps so we can
 address the problem quickly.
 
+### Running Tests
+
+The tests are most easily run from Docker. Check out the
+[`Dockerfile`](Dockerfile) for how that's set up.
+
+We push a pre-built image to Docker Hub as `dtrx-py/dtrx:latest`, so you can
+pull that image and run the tests like so:
+
+```bash
+docker run --rm -it --volume $(pwd):/workspace ghcr.io/dtrx-py/dtrx:latest \
+   bash -c "cp -r /workspace ~/scratch && cd ~/scratch && uv run -- 
tests/compare.py"
+```
+
 ### Releases
 
 Releases are tagged in this repo and published to pypi.org. The release process
@@ -101,18 +104,16 @@
 1. update the version specifier:
 
    ```bash
-   # update the VERSION value in dtrx/dtrx.py, then:
-   ❯ git add dtrx/dtrx.py
+   # update the version appropriately
+   ❯ uv version X.Y.Z
+   ❯ git add pyproject.toml uv.lock
    ❯ git commit  # fill in the commit message
    ```
 
-2. create an annotated tag for the release. usually good to put a list of new
-   commits since the previous tag, for example by listing them with:
+2. create an annotated tag for the release.
 
    ```bash
-   ❯ git log $(git describe --tags --abbrev=0)..HEAD --oneline
-   # create the annotated tag
-   ❯ git tag -a <version number>
+   ❯ git tag -a {,-m}X.Y.Z
    ```
 
    be sure to push the tag, `git push --tags`.
@@ -121,67 +122,3 @@
    PyPi
 
 See the [`Makefile`](Makefile) for details on what that rule does.
-
-### Invoke + Tests
-
-There's some minimal helper scripts for pyinvoke under [`tasks/`](tasks/).
-
-To bootstrap, run `pip install -r requirements.txt`, then `inv --list` to see
-available tasks:
-
-```bash
-❯ inv --list
-Available tasks:
-
-  build-docker                build docker image
-  push-docker                 push docker image
-  quick-test                  run quick tests in docker
-  rst2man                     run rst2man in docker
-  test-nonexistent-file-cmd   run test-nonexistent-file-cmd.sh
-  tox                         run tox in docker
-  windows                     just check that windows install fails. pulls a 
minimal wine docker image to test
-```
-
-To run the tests, run `inv tox`. Takes a couple of minutes to go through all 
the
-python versions.
-
-### Linting
-
-Linting is provided by [pre-commit](pre-commit.com). To use it, first install
-the pre-commit hook:
-
-```bash
-pip install pre-commit
-pre-commit install
-```
-
-pre-commit will run anytime `git commit` runs (disable with `--no-verify`). You
-can manually run it with `pre-commit run`.
-
-### Docker
-
-The tests in CI (and locally) can be run inside a Docker container, which
-provides all the tested python versions.
-
-This image is defined at [`Dockerfile`](Dockerfile). It's pushed to the GitHub
-Container Registry so it can be managed by the `dtrx-py` organization on 
GitHub-
-Docker Hub charges for Organizations.
-
-There are Invoke tasks for building + pushing the Docker image, which push both
-a `:latest` tag as well as a `:2022-09-16` ISO8601 numbered tag. The tag can
-then be updated in the GitHub actions runner.
-
-> Note: there's a bit of complexity around how the image is used, because the
-> dtrx tests need to run as a non-root user (there's one test that checks for
-> error handling when the output directory is not accessible by the current
-> user). To deal with this, there's an entrypoint script that switches user to 
a
-> non-root user, but that still has read/write access to the mounted host 
volume
-> (which is the cwd, intended for local development work). This is required on
-> Linux, where it's nice to have the host+container UID+GUID matching, so any
-> changes to the mounted host volume have the same permissions set.
->
-> In the GitHub actions runner, we need to run inside the same container (to
-> have access to the correct python versions for testing), and the github 
action
-> for checkout assumes it can write to somewhat arbitrary locations in the file
-> system (basically root access). So we switch to the non-root user _after_
-> checkout.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/dtrx.egg-info/SOURCES.txt 
new/dtrx-8.7.1/dtrx.egg-info/SOURCES.txt
--- old/dtrx-8.5.3/dtrx.egg-info/SOURCES.txt    2023-05-11 20:56:02.000000000 
+0200
+++ new/dtrx-8.7.1/dtrx.egg-info/SOURCES.txt    2025-12-01 19:23:07.000000000 
+0100
@@ -1,8 +1,6 @@
 COPYING
 README.md
 pyproject.toml
-setup.cfg
-setup.py
 dtrx/__init__.py
 dtrx/dtrx.py
 dtrx.egg-info/PKG-INFO
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/dtrx.egg-info/requires.txt 
new/dtrx-8.7.1/dtrx.egg-info/requires.txt
--- old/dtrx-8.5.3/dtrx.egg-info/requires.txt   2023-05-11 20:56:02.000000000 
+0200
+++ new/dtrx-8.7.1/dtrx.egg-info/requires.txt   2025-12-01 19:23:07.000000000 
+0100
@@ -1,6 +1,3 @@
 
 [:platform_system == "Windows"]
 unsupported-python==1.0.0
-
-[:python_version < "3.3"]
-subprocess32
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/pyproject.toml 
new/dtrx-8.7.1/pyproject.toml
--- old/dtrx-8.5.3/pyproject.toml       2022-03-29 04:05:22.000000000 +0200
+++ new/dtrx-8.7.1/pyproject.toml       2025-12-01 19:21:40.000000000 +0100
@@ -1,14 +1,52 @@
-[build-system]
-requires = [
-    # latest at time of writing
-    "setuptools >= 44.1.1",
-    "wheel >= 0.37.0",
+[project]
+name = "dtrx"
+version = "8.7.1"
+description = "Script to intelligently extract multiple archive types"
+readme = "README.md"
+license = "GPL-3.0-or-later"
+authors = [{ name = "Brett Smith", email = "[email protected]" }]
+maintainers = [{ name = "Brett Smith", email = "[email protected]" }]
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Environment :: Console",
+    "Intended Audience :: End Users/Desktop",
+    "Intended Audience :: System Administrators",
+    "Natural Language :: English",
+    "Operating System :: POSIX",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Topic :: Utilities",
+]
+requires-python = ">=3.10"
+dependencies = [
+    # Intentionally disable windows install, dtrx is not supported on windows
+    "unsupported-python==1.0.0; platform_system=='Windows'",
+]
+
+[project.urls]
+Homepage = "http://www.brettcsmith.org/2007/dtrx/";
+Repository = "https://github.com/dtrx-py/dtrx";
+Download = "https://github.com/dtrx-py/dtrx";
+
+[project.scripts]
+dtrx = "dtrx.dtrx:main"
+
+[dependency-groups]
+dev = [
+    "docutils==0.16",
+    "pyyaml==5.3.1",
+    "ruff>=0.14.3",
 ]
-build-backend = 'setuptools.build_meta'
 
-[tool.black]
+[tool.ruff]
+target-version = "py312"
 preview = true
-target-version = ['py38']
+line-length = 100
+
+[build-system]
+requires = ["setuptools>=75", "wheel"]
+build-backend = "setuptools.build_meta"
 
-[tool.isort]
-profile = 'black'
+[tool.setuptools.packages.find]
+include = ["dtrx*"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/setup.cfg new/dtrx-8.7.1/setup.cfg
--- old/dtrx-8.5.3/setup.cfg    2023-05-11 20:56:02.714164000 +0200
+++ new/dtrx-8.7.1/setup.cfg    2025-12-01 19:23:07.367947000 +0100
@@ -1,44 +1,3 @@
-[metadata]
-name = dtrx
-version = attr: dtrx.dtrx.VERSION
-description = Script to intelligently extract multiple archive types
-long_description = file: README.md
-long_description_content_type = text/markdown
-author = Brett Smith
-author_email = [email protected]
-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
-       Programming Language :: Python :: 2
-       Programming Language :: Python :: 3
-       Programming Language :: Python :: Implementation :: CPython
-       Topic :: Utilities
-download_url = https://github.com/dtrx-py/dtrx
-project_urls = 
-       homepage = http://www.brettcsmith.org/2007/dtrx/
-       code = https://github.com/dtrx-py/dtrx
-
-[options]
-packages = dtrx
-install_requires = 
-       subprocess32;python_version < '3.3'
-       unsupported-python==1.0.0;platform_system=="Windows"
-python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, 
!=3.6.*
-
-[options.entry_points]
-console_scripts = 
-       dtrx = dtrx.dtrx:main
-
-[bdist_wheel]
-universal = 1
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dtrx-8.5.3/setup.py new/dtrx-8.7.1/setup.py
--- old/dtrx-8.5.3/setup.py     2022-11-16 15:51:03.000000000 +0100
+++ new/dtrx-8.7.1/setup.py     1970-01-01 01:00:00.000000000 +0100
@@ -1,7 +0,0 @@
-"""
-Thin wrapper for backwards compatibility
-"""
-
-import setuptools
-
-setuptools.setup()

Reply via email to