Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-jaraco.text for 
openSUSE:Factory checked in at 2026-03-17 19:02:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jaraco.text (Old)
 and      /work/SRC/openSUSE:Factory/.python-jaraco.text.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-jaraco.text"

Tue Mar 17 19:02:05 2026 rev:18 rq:1339134 version:4.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-jaraco.text/python-jaraco.text.changes    
2025-04-11 16:45:38.804367832 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-jaraco.text.new.8177/python-jaraco.text.changes
  2026-03-17 19:02:12.698998759 +0100
@@ -1,0 +2,14 @@
+Sun Mar 15 15:35:56 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.2.0:
+  * Replaced autocommand usage with Typer -- by :user:`Avasam`
+  * Fixed join_continuation's parameter type annotation -- by
+    :user:`Avasam`
+  * Complete annotations and add py.typed marker -- by
+    :user:`Avasam`
+  * Add support for start and end in jaraco.text.FoldedCase.index
+    -- by :user:`Avasam`
+  * Support None splitter argument in
+    jaraco.text.FoldedCase.split -- by :user:`Avasam`
+
+-------------------------------------------------------------------

Old:
----
  jaraco_text-4.0.0.tar.gz

New:
----
  jaraco_text-4.2.0.tar.gz

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

Other differences:
------------------
++++++ python-jaraco.text.spec ++++++
--- /var/tmp/diff_new_pack.7lBdzE/_old  2026-03-17 19:02:13.575034683 +0100
+++ /var/tmp/diff_new_pack.7lBdzE/_new  2026-03-17 19:02:13.579034847 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-jaraco.text
 #
-# Copyright (c) 2025 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
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-jaraco.text
-Version:        4.0.0
+Version:        4.2.0
 Release:        0
 Summary:        Tools to work with text
 License:        MIT
@@ -34,6 +34,7 @@
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools_scm >= 3.4.1}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module typer}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
@@ -45,6 +46,7 @@
 Requires:       python-jaraco.context >= 4.1
 Requires:       python-jaraco.functools
 Requires:       python-more-itertools
+Requires:       python-typer
 BuildArch:      noarch
 %python_subpackages
 

++++++ jaraco_text-4.0.0.tar.gz -> jaraco_text-4.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/.coveragerc 
new/jaraco_text-4.2.0/.coveragerc
--- old/jaraco_text-4.0.0/.coveragerc   2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/.coveragerc   2026-02-10 00:29:39.000000000 +0100
@@ -8,6 +8,8 @@
 [report]
 show_missing = True
 exclude_also =
-       # jaraco/skeleton#97
-       @overload
+       # Exclude common false positives per
+       # 
https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion
+       # Ref jaraco/skeleton#97 and jaraco/skeleton#135
+       class .*\bProtocol\):
        if TYPE_CHECKING:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/.github/dependabot.yml 
new/jaraco_text-4.2.0/.github/dependabot.yml
--- old/jaraco_text-4.0.0/.github/dependabot.yml        2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/.github/dependabot.yml        1970-01-01 
01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-version: 2
-updates:
-  - package-ecosystem: "pip"
-    directory: "/"
-    schedule:
-      interval: "daily"
-    allow:
-      - dependency-type: "all"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/.github/workflows/main.yml 
new/jaraco_text-4.2.0/.github/workflows/main.yml
--- old/jaraco_text-4.0.0/.github/workflows/main.yml    2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/.github/workflows/main.yml    2026-02-10 
00:29:39.000000000 +0100
@@ -10,6 +10,7 @@
     # required if branches-ignore is supplied (jaraco/skeleton#103)
     - '**'
   pull_request:
+  workflow_dispatch:
 
 permissions:
   contents: read
@@ -20,7 +21,6 @@
 
   # Suppress noisy pip warnings
   PIP_DISABLE_PIP_VERSION_CHECK: 'true'
-  PIP_NO_PYTHON_VERSION_WARNING: 'true'
   PIP_NO_WARN_SCRIPT_LOCATION: 'true'
 
   # Ensure tests can sense settings about the environment
@@ -34,27 +34,36 @@
       # https://blog.jaraco.com/efficient-use-of-ci-resources/
       matrix:
         python:
-        - "3.8"
-        - "3.12"
+        - "3.9"
+        - "3.13"
         platform:
         - ubuntu-latest
         - macos-latest
         - windows-latest
         include:
-        - python: "3.9"
-          platform: ubuntu-latest
         - python: "3.10"
           platform: ubuntu-latest
         - python: "3.11"
           platform: ubuntu-latest
+        - python: "3.12"
+          platform: ubuntu-latest
+        - python: "3.14"
+          platform: ubuntu-latest
         - python: pypy3.10
           platform: ubuntu-latest
     runs-on: ${{ matrix.platform }}
-    continue-on-error: ${{ matrix.python == '3.13' }}
+    continue-on-error: ${{ matrix.python == '3.14' }}
     steps:
       - uses: actions/checkout@v4
+      - name: Install build dependencies
+        # Install dependencies for building packages on pre-release Pythons
+        # jaraco/skeleton#161
+        if: matrix.python == '3.14' && matrix.platform == 'ubuntu-latest'
+        run: |
+          sudo apt update
+          sudo apt install -y libxml2-dev libxslt-dev
       - name: Setup Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python }}
           allow-prereleases: true
@@ -76,7 +85,7 @@
         with:
           fetch-depth: 0
       - name: Setup Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.x
       - name: Install tox
@@ -110,7 +119,7 @@
     steps:
       - uses: actions/checkout@v4
       - name: Setup Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.x
       - name: Install tox
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/.pre-commit-config.yaml 
new/jaraco_text-4.2.0/.pre-commit-config.yaml
--- old/jaraco_text-4.0.0/.pre-commit-config.yaml       2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/.pre-commit-config.yaml       2026-02-10 
00:29:39.000000000 +0100
@@ -1,6 +1,7 @@
 repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.1.8
+  rev: v0.12.0
   hooks:
   - id: ruff
+    args: [--fix, --unsafe-fixes]
   - id: ruff-format
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/.readthedocs.yaml 
new/jaraco_text-4.2.0/.readthedocs.yaml
--- old/jaraco_text-4.0.0/.readthedocs.yaml     2024-07-26 20:08:11.000000000 
+0200
+++ new/jaraco_text-4.2.0/.readthedocs.yaml     2026-02-10 00:29:39.000000000 
+0100
@@ -5,6 +5,9 @@
     extra_requirements:
       - doc
 
+sphinx:
+  configuration: docs/conf.py
+
 # required boilerplate readthedocs/readthedocs.org#10401
 build:
   os: ubuntu-lts-latest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/LICENSE 
new/jaraco_text-4.2.0/LICENSE
--- old/jaraco_text-4.0.0/LICENSE       2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/LICENSE       2026-02-10 00:30:01.000000000 +0100
@@ -1,17 +1,18 @@
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to
-deal in the Software without restriction, including without limitation the
-rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-sell copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+MIT License
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+Copyright (c) 2026 <copyright holders>
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
+Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and
+associated documentation files (the "Software"), to deal in the Software 
without restriction, including
+without limitation the rights to use, copy, modify, merge, publish, 
distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
AND NONINFRINGEMENT. IN NO
+EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 
OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/NEWS.rst 
new/jaraco_text-4.2.0/NEWS.rst
--- old/jaraco_text-4.0.0/NEWS.rst      2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/NEWS.rst      2026-02-10 00:29:39.000000000 +0100
@@ -1,3 +1,29 @@
+v4.2.0
+======
+
+Features
+--------
+
+- Replaced ``autocommand`` usage with ``Typer`` -- by :user:`Avasam` (#25)
+
+
+Bugfixes
+--------
+
+- Fixed `join_continuation`'s parameter type annotation -- by :user:`Avasam` 
(#28)
+
+
+v4.1.0
+======
+
+Features
+--------
+
+- Complete annotations and add ``py.typed`` marker -- by :user:`Avasam` (#17)
+- Add support for ``start`` and ``end`` in `jaraco.text.FoldedCase.index` -- 
by :user:`Avasam` (#17)
+- Support `None` ``splitter`` argument in `jaraco.text.FoldedCase.split` -- by 
:user:`Avasam` (#17)
+
+
 v4.0.0
 ======
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/PKG-INFO 
new/jaraco_text-4.2.0/PKG-INFO
--- old/jaraco_text-4.0.0/PKG-INFO      2024-07-26 20:08:35.644393700 +0200
+++ new/jaraco_text-4.2.0/PKG-INFO      2026-02-10 00:30:02.087462400 +0100
@@ -1,29 +1,23 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: jaraco.text
-Version: 4.0.0
+Version: 4.2.0
 Summary: Module for text manipulation
 Author-email: "Jason R. Coombs" <[email protected]>
+License-Expression: MIT
 Project-URL: Source, https://github.com/jaraco/jaraco.text
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/x-rst
 License-File: LICENSE
-Requires-Dist: jaraco.functools
 Requires-Dist: jaraco.context>=4.1
-Requires-Dist: importlib_resources; python_version < "3.9"
-Requires-Dist: autocommand
+Requires-Dist: jaraco.functools
 Requires-Dist: more_itertools
+Requires-Dist: typer-slim
 Provides-Extra: test
 Requires-Dist: pytest!=8.1.*,>=6; extra == "test"
-Requires-Dist: pytest-checkdocs>=2.4; extra == "test"
-Requires-Dist: pytest-cov; extra == "test"
-Requires-Dist: pytest-mypy; extra == "test"
-Requires-Dist: pytest-enabler>=2.2; extra == "test"
-Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "test"
 Requires-Dist: pathlib2; python_version < "3.10" and extra == "test"
 Provides-Extra: doc
 Requires-Dist: sphinx>=3.5; extra == "doc"
@@ -34,6 +28,17 @@
 Requires-Dist: jaraco.tidelift>=1.4; extra == "doc"
 Provides-Extra: inflect
 Requires-Dist: inflect; extra == "inflect"
+Provides-Extra: check
+Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
+Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == 
"check"
+Provides-Extra: cover
+Requires-Dist: pytest-cov; extra == "cover"
+Provides-Extra: enabler
+Requires-Dist: pytest-enabler>=3.4; extra == "enabler"
+Provides-Extra: type
+Requires-Dist: pytest-mypy>=1.0.1; extra == "type"
+Requires-Dist: mypy<1.19; platform_python_implementation == "PyPy" and extra 
== "type"
+Dynamic: license-file
 
 .. image:: https://img.shields.io/pypi/v/jaraco.text.svg
    :target: https://pypi.org/project/jaraco.text
@@ -44,14 +49,14 @@
    :target: 
https://github.com/jaraco/jaraco.text/actions?query=workflow%3A%22tests%22
    :alt: tests
 
-.. image:: 
https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
+.. image:: 
https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
     :target: https://github.com/astral-sh/ruff
     :alt: Ruff
 
 .. image:: https://readthedocs.org/projects/jaracotext/badge/?version=latest
    :target: https://jaracotext.readthedocs.io/en/latest/?badge=latest
 
-.. image:: https://img.shields.io/badge/skeleton-2024-informational
+.. image:: https://img.shields.io/badge/skeleton-2025-informational
    :target: https://blog.jaraco.com/skeleton
 
 .. image:: https://tidelift.com/badges/package/pypi/jaraco.text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/README.rst 
new/jaraco_text-4.2.0/README.rst
--- old/jaraco_text-4.0.0/README.rst    2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/README.rst    2026-02-10 00:29:39.000000000 +0100
@@ -7,14 +7,14 @@
    :target: 
https://github.com/jaraco/jaraco.text/actions?query=workflow%3A%22tests%22
    :alt: tests
 
-.. image:: 
https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
+.. image:: 
https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
     :target: https://github.com/astral-sh/ruff
     :alt: Ruff
 
 .. image:: https://readthedocs.org/projects/jaracotext/badge/?version=latest
    :target: https://jaracotext.readthedocs.io/en/latest/?badge=latest
 
-.. image:: https://img.shields.io/badge/skeleton-2024-informational
+.. image:: https://img.shields.io/badge/skeleton-2025-informational
    :target: https://blog.jaraco.com/skeleton
 
 .. image:: https://tidelift.com/badges/package/pypi/jaraco.text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/conftest.py 
new/jaraco_text-4.2.0/conftest.py
--- old/jaraco_text-4.0.0/conftest.py   2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/conftest.py   2026-02-10 00:29:39.000000000 +0100
@@ -1,3 +1,4 @@
+import pathlib as std_pathlib
 import sys
 
 import pytest
@@ -5,11 +6,11 @@
 if sys.version_info < (3, 10):
     import pathlib2 as pathlib  # pragma: nocover
 else:
-    import pathlib  # pragma: nocover
+    pathlib = std_pathlib  # pragma: nocover
 
 
 @pytest.fixture
-def tmp_path(tmp_path):
+def tmp_path(tmp_path: std_pathlib.Path) -> pathlib.Path:
     """
     Override tmp_path to wrap in a more modern interface.
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/docs/conf.py 
new/jaraco_text-4.2.0/docs/conf.py
--- old/jaraco_text-4.0.0/docs/conf.py  2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/docs/conf.py  2026-02-10 00:29:39.000000000 +0100
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 extensions = [
     'sphinx.ext.autodoc',
     'jaraco.packaging.sphinx',
@@ -30,6 +32,7 @@
 
 # Be strict about any broken references
 nitpicky = True
+nitpick_ignore: list[tuple[str, str]] = []
 
 # Include Python intersphinx mapping to prevent failures
 # jaraco/skeleton#51
@@ -41,4 +44,26 @@
 # Preserve authored syntax for defaults
 autodoc_preserve_defaults = True
 
+# Add support for linking usernames, PyPI projects, Wikipedia pages
+github_url = 'https://github.com/'
+extlinks = {
+    'user': (f'{github_url}%s', '@%s'),
+    'pypi': ('https://pypi.org/project/%s', '%s'),
+    'wiki': ('https://wikipedia.org/wiki/%s', '%s'),
+}
+extensions += ['sphinx.ext.extlinks']
+
+# local
+
 extensions += ['jaraco.tidelift']
+nitpick_ignore += [
+    ("py:class", "FileDescriptorOrPath"),
+    ("py:class", "_SupportsDecode"),
+    ("py:class", "_TranslateTable"),
+    ("py:class", "SupportsGetItem"),
+    ("py:class", "SupportsNext"),
+    ("py:class", "SupportsIter"),
+    ("py:class", "itertools.chain"),
+    ("py:class", "jaraco.text._SupportsDecode"),
+    ("py:class", "jaraco.text._T"),
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco/text/__init__.py 
new/jaraco_text-4.2.0/jaraco/text/__init__.py
--- old/jaraco_text-4.0.0/jaraco/text/__init__.py       2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco/text/__init__.py       2026-02-10 
00:29:39.000000000 +0100
@@ -1,27 +1,60 @@
+from __future__ import annotations
+
 import functools
+import io
 import itertools
+import os
 import re
+import sys
 import textwrap
-
-from typing import Iterable
-
-try:
-    from importlib.resources import files  # type: ignore
-except ImportError:  # pragma: nocover
-    from importlib_resources import files  # type: ignore
+from collections.abc import Callable, Generator, Iterable, Sequence
+from importlib.resources import files
+from typing import (
+    TYPE_CHECKING,
+    Literal,
+    Protocol,
+    SupportsIndex,
+    TypeVar,
+    Union,
+    cast,
+    overload,
+)
 
 from jaraco.context import ExceptionTrap
 from jaraco.functools import compose, method_cache
 
+if sys.version_info >= (3, 11):  # pragma: no cover
+    from importlib.resources.abc import Traversable
+else:  # pragma: no cover
+    from importlib.abc import Traversable
+
+if TYPE_CHECKING:
+    from _typeshed import (
+        FileDescriptorOrPath,
+        SupportsIter,
+        SupportsNext,
+    )
+    from typing_extensions import Self, TypeAlias, TypeGuard, Unpack
+
+    Openable: TypeAlias = FileDescriptorOrPath
+else:
+    Openable = Union[str, bytes, os.PathLike, int]
+
+_T = TypeVar("_T")
 
-def substitution(old, new):
+
+class _SupportsDecode(Protocol):
+    def decode(self) -> object: ...
+
+
+def substitution(old: str, new: str) -> Callable[[str], str]:
     """
     Return a function that will perform a substitution on a string
     """
     return lambda s: s.replace(old, new)
 
 
-def multi_substitution(*substitutions):
+def multi_substitution(*substitutions: str) -> Callable[[str], str]:
     """
     Take a sequence of pairs specifying substitutions, and create
     a function that performs those substitutions.
@@ -29,11 +62,13 @@
     >>> multi_substitution(('foo', 'bar'), ('bar', 'baz'))('foo')
     'baz'
     """
-    substitutions = itertools.starmap(substitution, substitutions)
+    callables: Iterable[Callable[[str], str]] = itertools.starmap(
+        substitution, substitutions
+    )
     # compose function applies last function first, so reverse the
     #  substitutions to get the expected order.
-    substitutions = reversed(tuple(substitutions))
-    return compose(*substitutions)
+    reversed_ = reversed(tuple(callables))
+    return compose(*reversed_)
 
 
 class FoldedCase(str):
@@ -58,6 +93,11 @@
     >>> s.split('O')
     ['hell', ' w', 'rld']
 
+    Like ``str``, split accepts None as ''.
+
+    >>> s.split(None)
+    ['hello', 'world']
+
     >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
     ['alpha', 'Beta', 'GAMMA']
 
@@ -97,49 +137,81 @@
 
     >>> FoldedCase('ß') == FoldedCase('ss')
     True
+
+    Also supports string to object comparisons:
+
+    >>> FoldedCase('foo') == object()
+    False
+    >>> FoldedCase('foo') != object()
+    True
+    >>> object() in FoldedCase('foo')
+    False
     """
 
-    def __lt__(self, other):
+    def __lt__(self, other: str) -> bool:
         return self.casefold() < other.casefold()
 
-    def __gt__(self, other):
+    def __gt__(self, other: str) -> bool:
         return self.casefold() > other.casefold()
 
-    def __eq__(self, other):
-        return self.casefold() == other.casefold()
-
-    def __ne__(self, other):
-        return self.casefold() != other.casefold()
+    @functools.singledispatchmethod
+    def __eq__(self, other: object) -> bool:
+        return False
+
+    @__eq__.register
+    def _(self, other: str) -> bool:
+        return self.casefold().__eq__(other.casefold())
+
+    @functools.singledispatchmethod
+    def __ne__(self, other: object) -> bool:
+        return True
+
+    @__ne__.register
+    def _(self, other: str) -> bool:
+        return self.casefold().__ne__(other.casefold())
 
-    def __hash__(self):
+    def __hash__(self) -> int:
         return hash(self.casefold())
 
-    def __contains__(self, other):
+    @functools.singledispatchmethod
+    def __contains__(self, other: object) -> bool:
+        return False
+
+    @__contains__.register
+    def _(self, other: str) -> bool:
         return super().casefold().__contains__(other.casefold())
 
-    def in_(self, other):
-        "Does self appear in other?"
+    def in_(self, other: str) -> bool:
+        """Does self appear in other?"""
         return self in FoldedCase(other)
 
     # cache casefold since it's likely to be called frequently.
     @method_cache
-    def casefold(self):
+    def casefold(self) -> str:
         return super().casefold()
 
-    def index(self, sub):
-        return self.casefold().index(sub.casefold())
+    def index(
+        self,
+        sub: str,
+        start: SupportsIndex | None = None,
+        end: SupportsIndex | None = None,
+    ) -> int:
+        return self.casefold().index(sub.casefold(), start, end)
+
+    @functools.singledispatchmethod
+    def split(
+        self, splitter: str | None = ' ', maxsplit: SupportsIndex = 0
+    ) -> list[str]:
+        return self.split(' ', maxsplit=maxsplit)
 
-    def split(self, splitter=' ', maxsplit=0):
+    @split.register
+    def _(self, splitter: str, maxsplit: SupportsIndex = 0) -> list[str]:
         pattern = re.compile(re.escape(splitter), re.I)
-        return pattern.split(self, maxsplit)
-
-
-# Python 3.8 compatibility
-_unicode_trap = ExceptionTrap(UnicodeDecodeError)
+        return pattern.split(self, int(maxsplit))
 
 
-@_unicode_trap.passes
-def is_decodable(value):
+@ExceptionTrap(UnicodeDecodeError).passes  # type: ignore[no-untyped-call, 
untyped-decorator, unused-ignore, misc] # jaraco/jaraco.context#15
+def is_decodable(value: _SupportsDecode) -> None:
     r"""
     Return True if the supplied value is decodable (using the default
     encoding).
@@ -152,7 +224,7 @@
     value.decode()
 
 
-def is_binary(value):
+def is_binary(value: _SupportsDecode) -> TypeGuard[bytes]:
     r"""
     Return True if the value appears to be binary (that is, it's a byte
     string and isn't decodable).
@@ -165,7 +237,7 @@
     return isinstance(value, bytes) and not is_decodable(value)
 
 
-def trim(s):
+def trim(s: str) -> str:
     r"""
     Trim something like a docstring to remove the whitespace that
     is common due to indentation and formatting.
@@ -176,7 +248,7 @@
     return textwrap.dedent(s).strip()
 
 
-def wrap(s):
+def wrap(s: str) -> str:
     """
     Wrap lines of text, retaining existing newlines as
     paragraph markers.
@@ -209,7 +281,7 @@
     return '\n\n'.join(wrapped)
 
 
-def unwrap(s):
+def unwrap(s: str) -> str:
     r"""
     Given a multi-line string, return an unwrapped version.
 
@@ -242,14 +314,14 @@
     ['hello', ' world', ' this is your', ' master calling']
     """
 
-    def __init__(self, *args):
+    def __init__(self, *args: Unpack[tuple[str | None, SupportsIndex]]) -> 
None:
         self.args = args
 
-    def __call__(self, s):
+    def __call__(self, s: str) -> list[str]:
         return s.split(*self.args)
 
 
-def indent(string, prefix=' ' * 4):
+def indent(string: str, prefix: str = ' ' * 4) -> str:
     """
     >>> indent('foo')
     '    foo'
@@ -257,7 +329,7 @@
     return prefix + string
 
 
-class WordSet(tuple):
+class WordSet(tuple[str, ...]):
     """
     Given an identifier, return the words that identifier represents,
     whether in camel case, underscore-separated, etc.
@@ -313,31 +385,31 @@
 
     _pattern = re.compile('([A-Z]?[a-z]+)|([A-Z]+(?![a-z]))')
 
-    def capitalized(self):
+    def capitalized(self) -> WordSet:
         return WordSet(word.capitalize() for word in self)
 
-    def lowered(self):
+    def lowered(self) -> WordSet:
         return WordSet(word.lower() for word in self)
 
-    def camel_case(self):
+    def camel_case(self) -> str:
         return ''.join(self.capitalized())
 
-    def headless_camel_case(self):
+    def headless_camel_case(self) -> str:
         words = iter(self)
         first = next(words).lower()
         new_words = itertools.chain((first,), WordSet(words).camel_case())
         return ''.join(new_words)
 
-    def underscore_separated(self):
+    def underscore_separated(self) -> str:
         return '_'.join(self)
 
-    def dash_separated(self):
+    def dash_separated(self) -> str:
         return '-'.join(self)
 
-    def space_separated(self):
+    def space_separated(self) -> str:
         return ' '.join(self)
 
-    def trim_right(self, item):
+    def trim_right(self, item: str) -> WordSet:
         """
         Remove the item from the end of the set.
 
@@ -350,7 +422,7 @@
         """
         return self[:-1] if self and self[-1] == item else self
 
-    def trim_left(self, item):
+    def trim_left(self, item: str) -> WordSet:
         """
         Remove the item from the beginning of the set.
 
@@ -363,26 +435,30 @@
         """
         return self[1:] if self and self[0] == item else self
 
-    def trim(self, item):
+    def trim(self, item: str) -> WordSet:
         """
         >>> WordSet.parse('foo bar').trim('foo')
         ('bar',)
         """
         return self.trim_left(item).trim_right(item)
 
-    def __getitem__(self, item):
+    @overload  # type:ignore[override] # more restricted return type
+    def __getitem__(self, item: slice) -> WordSet: ...
+    @overload
+    def __getitem__(self, item: SupportsIndex) -> str: ...
+    def __getitem__(self, item: slice | SupportsIndex) -> WordSet | str:
         result = super().__getitem__(item)
-        if isinstance(item, slice):
-            result = WordSet(result)
+        if isinstance(result, tuple):
+            return WordSet(result)
         return result
 
     @classmethod
-    def parse(cls, identifier):
+    def parse(cls, identifier: str) -> WordSet:
         matches = cls._pattern.finditer(identifier)
         return WordSet(match.group(0) for match in matches)
 
     @classmethod
-    def from_class_name(cls, subject):
+    def from_class_name(cls, subject: object) -> WordSet:
         return cls.parse(subject.__class__.__name__)
 
 
@@ -390,7 +466,7 @@
 words = WordSet.parse
 
 
-def simple_html_strip(s):
+def simple_html_strip(s: str) -> str:
     r"""
     Remove HTML from the string `s`.
 
@@ -428,7 +504,7 @@
 
     separator = ','
 
-    def __iter__(self):
+    def __iter__(self) -> filter[str]:
         parts = self.split(self.separator)
         return filter(None, (part.strip() for part in parts))
 
@@ -460,24 +536,30 @@
     ['abcd\n', '1234\n']
     """
 
-    def __init__(self, prefix, lines):
+    def __init__(self, prefix: str | None, lines: Iterable[str]) -> None:
         self.prefix = prefix
         self.lines = map(self, lines)
 
     @classmethod
-    def strip_prefix(cls, lines):
+    def strip_prefix(cls, lines: Iterable[str]) -> Self:
         prefix_lines, lines = itertools.tee(lines)
         prefix = functools.reduce(cls.common_prefix, prefix_lines)
         return cls(prefix, lines)
 
-    def __call__(self, line):
+    def __call__(self, line: str) -> str:
         if not self.prefix:
             return line
         null, prefix, rest = line.partition(self.prefix)
         return rest
 
+    @overload
     @staticmethod
-    def common_prefix(s1, s2):
+    def common_prefix(s1: str, s2: str) -> str: ...
+    @overload
+    @staticmethod
+    def common_prefix(s1: Sequence[str], s2: Sequence[str]) -> Sequence[str]: 
...
+    @staticmethod
+    def common_prefix(s1: Sequence[str], s2: Sequence[str]) -> Sequence[str]:
         """
         Return the common prefix of two lines.
         """
@@ -487,7 +569,7 @@
         return s1[:index]
 
 
-def remove_prefix(text, prefix):
+def remove_prefix(text: str, prefix: str) -> str:
     """
     Remove the prefix from the text if it exists.
 
@@ -501,7 +583,7 @@
     return rest
 
 
-def remove_suffix(text, suffix):
+def remove_suffix(text: str, suffix: str) -> str:
     """
     Remove the suffix from the text if it exists.
 
@@ -515,7 +597,7 @@
     return rest
 
 
-def normalize_newlines(text):
+def normalize_newlines(text: str) -> str:
     r"""
     Replace alternate newlines with the canonical newline.
 
@@ -531,12 +613,12 @@
     return re.sub(pattern, '\n', text)
 
 
-def _nonblank(str):
+def _nonblank(str: str) -> bool | Literal['']:
     return str and not str.startswith('#')
 
 
 @functools.singledispatch
-def yield_lines(iterable):
+def yield_lines(iterable: Iterable[_T] | str) -> itertools.chain[str]:
     r"""
     Yield valid lines of a string or iterable.
 
@@ -555,18 +637,18 @@
 
 
 @yield_lines.register(str)
-def _(text):
+def _(text: str) -> filter[str]:
     return clean(text.splitlines())
 
 
-def clean(lines: Iterable[str]):
+def clean(lines: Iterable[str]) -> filter[str]:
     """
     Yield non-blank, non-comment elements from lines.
     """
     return filter(_nonblank, map(str.strip, lines))
 
 
-def drop_comment(line):
+def drop_comment(line: str) -> str:
     """
     Drop comments.
 
@@ -581,7 +663,7 @@
     return line.partition(' #')[0]
 
 
-def join_continuation(lines):
+def join_continuation(lines: SupportsIter[SupportsNext[str]]) -> 
Generator[str]:
     r"""
     Join lines continued by a trailing backslash.
 
@@ -604,17 +686,25 @@
     >>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
     ['foo']
     """
-    lines = iter(lines)
-    for item in lines:
+    lines_ = iter(lines)
+    for item in lines_:  # type: ignore[attr-defined] # A bit of a false 
positive with iteration dunder fallback
         while item.endswith('\\'):
             try:
-                item = item[:-2].strip() + next(lines)
+                item = item[:-2].strip() + next(lines_)
             except StopIteration:
                 return
         yield item
 
 
-def read_newlines(filename, limit=1024):
+# https://docs.python.org/3/library/io.html#io.TextIOBase.newlines
+NewlineSpec: TypeAlias = Union[str, tuple[str, ...], None]
+
+
[email protected]
+def read_newlines(
+    filename: Union[Openable, io.TextIOWrapper],  # noqa: UP007 # 
singledispatch uses the annotation at runtime (python 3.9)
+    limit: int | None = 1024,
+) -> NewlineSpec:
     r"""
     >>> tmp_path = getfixture('tmp_path')
     >>> filename = tmp_path / 'out.txt'
@@ -628,12 +718,24 @@
     >>> read_newlines(filename)
     ('\r', '\n', '\r\n')
     """
+    if sys.version_info >= (3, 10):
+        assert isinstance(filename, Openable)
+    else:  # pragma: no cover
+        filename = cast(Openable, filename)
     with open(filename, encoding='utf-8') as fp:
-        fp.read(limit)
-    return fp.newlines
+        return read_newlines(fp, limit=limit)
+
+
+@read_newlines.register
+def _(
+    filename: io.TextIOWrapper,
+    limit: Union[int, None] = 1024,  # noqa: UP007 # singledispatch uses the 
annotation at runtime (python 3.9)
+) -> NewlineSpec:
+    filename.read(limit)
+    return filename.newlines
 
 
-def lines_from(input):
+def lines_from(input: Traversable) -> Generator[str]:
     """
     Generate lines from a :class:`importlib.resources.abc.Traversable` path.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco/text/layouts.py 
new/jaraco_text-4.2.0/jaraco/text/layouts.py
--- old/jaraco_text-4.0.0/jaraco/text/layouts.py        2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco/text/layouts.py        2026-02-10 
00:29:39.000000000 +0100
@@ -1,3 +1,14 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Union
+
+if TYPE_CHECKING:
+    from _typeshed import SupportsGetItem, SupportsRead
+    from typing_extensions import TypeAlias
+
+    # Same as builtins._TranslateTable from typeshed
+    _TranslateTable: TypeAlias = SupportsGetItem[int, Union[str, int, None]]
+
 qwerty = 
"-=qwertyuiop[]asdfghjkl;'zxcvbnm,./_+QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>?"
 dvorak = 
"[]',.pyfgcrl/=aoeuidhtns-;qjkxbmwvz{}\"<>PYFGCRL?+AOEUIDHTNS_:QJKXBMWVZ"
 
@@ -6,7 +17,7 @@
 to_qwerty = str.maketrans(dvorak, qwerty)
 
 
-def translate(input, translation):
+def translate(input: str, translation: _TranslateTable) -> str:
     """
     >>> translate('dvorak', to_dvorak)
     'ekrpat'
@@ -16,7 +27,7 @@
     return input.translate(translation)
 
 
-def _translate_stream(stream, translation):
+def _translate_stream(stream: SupportsRead[str], translation: _TranslateTable) 
-> None:
     """
     >>> import io
     >>> _translate_stream(io.StringIO('foo'), to_dvorak)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco/text/show-newlines.py 
new/jaraco_text-4.2.0/jaraco/text/show-newlines.py
--- old/jaraco_text-4.0.0/jaraco/text/show-newlines.py  2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco/text/show-newlines.py  2026-02-10 
00:29:39.000000000 +0100
@@ -1,11 +1,13 @@
-import autocommand
+from __future__ import annotations
+
 import inflect
+import typer
 from more_itertools import always_iterable
 
 import jaraco.text
 
 
-def report_newlines(filename):
+def report_newlines(input: typer.FileText) -> None:
     r"""
     Report the newlines in the indicated file.
 
@@ -19,14 +21,15 @@
     >>> report_newlines(filename)
     newlines are ('\n', '\r\n')
     """
-    newlines = jaraco.text.read_newlines(filename)
+    newlines = jaraco.text.read_newlines(input)
     count = len(tuple(always_iterable(newlines)))
     engine = inflect.engine()
     print(
+        # Pyright typing issue: jaraco/inflect#210
         engine.plural_noun("newline", count),
         engine.plural_verb("is", count),
         repr(newlines),
     )
 
 
-autocommand.autocommand(__name__)(report_newlines)
+__name__ == '__main__' and typer.run(report_newlines)  # type: 
ignore[func-returns-value]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco/text/strip-prefix.py 
new/jaraco_text-4.2.0/jaraco/text/strip-prefix.py
--- old/jaraco_text-4.0.0/jaraco/text/strip-prefix.py   2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco/text/strip-prefix.py   2026-02-10 
00:29:39.000000000 +0100
@@ -1,11 +1,11 @@
 import sys
 
-import autocommand
+import typer
 
 from jaraco.text import Stripper
 
 
-def strip_prefix():
+def strip_prefix() -> None:
     r"""
     Strip any common prefix from stdin.
 
@@ -18,4 +18,4 @@
     sys.stdout.writelines(Stripper.strip_prefix(sys.stdin).lines)
 
 
-autocommand.autocommand(__name__)(strip_prefix)
+__name__ == '__main__' and typer.run(strip_prefix)  # type: 
ignore[func-returns-value]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco/text/to-dvorak.py 
new/jaraco_text-4.2.0/jaraco/text/to-dvorak.py
--- old/jaraco_text-4.0.0/jaraco/text/to-dvorak.py      2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco/text/to-dvorak.py      2026-02-10 
00:29:39.000000000 +0100
@@ -2,4 +2,4 @@
 
 from . import layouts
 
-__name__ == '__main__' and layouts._translate_stream(sys.stdin, 
layouts.to_dvorak)
+__name__ == '__main__' and layouts._translate_stream(sys.stdin, 
layouts.to_dvorak)  # type: ignore[func-returns-value]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco/text/to-qwerty.py 
new/jaraco_text-4.2.0/jaraco/text/to-qwerty.py
--- old/jaraco_text-4.0.0/jaraco/text/to-qwerty.py      2024-07-26 
20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco/text/to-qwerty.py      2026-02-10 
00:29:39.000000000 +0100
@@ -2,4 +2,4 @@
 
 from . import layouts
 
-__name__ == '__main__' and layouts._translate_stream(sys.stdin, 
layouts.to_qwerty)
+__name__ == '__main__' and layouts._translate_stream(sys.stdin, 
layouts.to_qwerty)  # type: ignore[func-returns-value]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco.text.egg-info/PKG-INFO 
new/jaraco_text-4.2.0/jaraco.text.egg-info/PKG-INFO
--- old/jaraco_text-4.0.0/jaraco.text.egg-info/PKG-INFO 2024-07-26 
20:08:35.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco.text.egg-info/PKG-INFO 2026-02-10 
00:30:02.000000000 +0100
@@ -1,29 +1,23 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: jaraco.text
-Version: 4.0.0
+Version: 4.2.0
 Summary: Module for text manipulation
 Author-email: "Jason R. Coombs" <[email protected]>
+License-Expression: MIT
 Project-URL: Source, https://github.com/jaraco/jaraco.text
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/x-rst
 License-File: LICENSE
-Requires-Dist: jaraco.functools
 Requires-Dist: jaraco.context>=4.1
-Requires-Dist: importlib_resources; python_version < "3.9"
-Requires-Dist: autocommand
+Requires-Dist: jaraco.functools
 Requires-Dist: more_itertools
+Requires-Dist: typer-slim
 Provides-Extra: test
 Requires-Dist: pytest!=8.1.*,>=6; extra == "test"
-Requires-Dist: pytest-checkdocs>=2.4; extra == "test"
-Requires-Dist: pytest-cov; extra == "test"
-Requires-Dist: pytest-mypy; extra == "test"
-Requires-Dist: pytest-enabler>=2.2; extra == "test"
-Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "test"
 Requires-Dist: pathlib2; python_version < "3.10" and extra == "test"
 Provides-Extra: doc
 Requires-Dist: sphinx>=3.5; extra == "doc"
@@ -34,6 +28,17 @@
 Requires-Dist: jaraco.tidelift>=1.4; extra == "doc"
 Provides-Extra: inflect
 Requires-Dist: inflect; extra == "inflect"
+Provides-Extra: check
+Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
+Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == 
"check"
+Provides-Extra: cover
+Requires-Dist: pytest-cov; extra == "cover"
+Provides-Extra: enabler
+Requires-Dist: pytest-enabler>=3.4; extra == "enabler"
+Provides-Extra: type
+Requires-Dist: pytest-mypy>=1.0.1; extra == "type"
+Requires-Dist: mypy<1.19; platform_python_implementation == "PyPy" and extra 
== "type"
+Dynamic: license-file
 
 .. image:: https://img.shields.io/pypi/v/jaraco.text.svg
    :target: https://pypi.org/project/jaraco.text
@@ -44,14 +49,14 @@
    :target: 
https://github.com/jaraco/jaraco.text/actions?query=workflow%3A%22tests%22
    :alt: tests
 
-.. image:: 
https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
+.. image:: 
https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
     :target: https://github.com/astral-sh/ruff
     :alt: Ruff
 
 .. image:: https://readthedocs.org/projects/jaracotext/badge/?version=latest
    :target: https://jaracotext.readthedocs.io/en/latest/?badge=latest
 
-.. image:: https://img.shields.io/badge/skeleton-2024-informational
+.. image:: https://img.shields.io/badge/skeleton-2025-informational
    :target: https://blog.jaraco.com/skeleton
 
 .. image:: https://tidelift.com/badges/package/pypi/jaraco.text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco.text.egg-info/SOURCES.txt 
new/jaraco_text-4.2.0/jaraco.text.egg-info/SOURCES.txt
--- old/jaraco_text-4.0.0/jaraco.text.egg-info/SOURCES.txt      2024-07-26 
20:08:35.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco.text.egg-info/SOURCES.txt      2026-02-10 
00:30:02.000000000 +0100
@@ -14,7 +14,6 @@
 towncrier.toml
 tox.ini
 .github/FUNDING.yml
-.github/dependabot.yml
 .github/workflows/main.yml
 docs/conf.py
 docs/history.rst
@@ -27,7 +26,9 @@
 jaraco/text/Lorem ipsum.txt
 jaraco/text/__init__.py
 jaraco/text/layouts.py
+jaraco/text/py.typed
 jaraco/text/show-newlines.py
 jaraco/text/strip-prefix.py
 jaraco/text/to-dvorak.py
-jaraco/text/to-qwerty.py
\ No newline at end of file
+jaraco/text/to-qwerty.py
+tests/test_core.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/jaraco.text.egg-info/requires.txt 
new/jaraco_text-4.2.0/jaraco.text.egg-info/requires.txt
--- old/jaraco_text-4.0.0/jaraco.text.egg-info/requires.txt     2024-07-26 
20:08:35.000000000 +0200
+++ new/jaraco_text-4.2.0/jaraco.text.egg-info/requires.txt     2026-02-10 
00:30:02.000000000 +0100
@@ -1,10 +1,16 @@
-jaraco.functools
 jaraco.context>=4.1
-autocommand
+jaraco.functools
 more_itertools
+typer-slim
+
+[check]
+pytest-checkdocs>=2.4
+
+[check:sys_platform != "cygwin"]
+pytest-ruff>=0.2.1
 
-[:python_version < "3.9"]
-importlib_resources
+[cover]
+pytest-cov
 
 [doc]
 sphinx>=3.5
@@ -14,18 +20,20 @@
 sphinx-lint
 jaraco.tidelift>=1.4
 
+[enabler]
+pytest-enabler>=3.4
+
 [inflect]
 inflect
 
 [test]
 pytest!=8.1.*,>=6
-pytest-checkdocs>=2.4
-pytest-cov
-pytest-mypy
-pytest-enabler>=2.2
 
 [test:python_version < "3.10"]
 pathlib2
 
-[test:sys_platform != "cygwin"]
-pytest-ruff>=0.2.1
+[type]
+pytest-mypy>=1.0.1
+
+[type:platform_python_implementation == "PyPy"]
+mypy<1.19
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/mypy.ini 
new/jaraco_text-4.2.0/mypy.ini
--- old/jaraco_text-4.0.0/mypy.ini      2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/mypy.ini      2026-02-10 00:29:39.000000000 +0100
@@ -1,5 +1,24 @@
 [mypy]
-ignore_missing_imports = True
-# required to support namespace packages
-# https://github.com/python/mypy/issues/14057
+# Is the project well-typed?
+strict = True
+
+# Early opt-in even when strict = False
+warn_unused_ignores = True
+warn_redundant_casts = True
+enable_error_code = ignore-without-code
+
+# Support namespace packages per https://github.com/python/mypy/issues/14057
 explicit_package_bases = True
+
+disable_error_code =
+       # Disable due to many false positives
+       overload-overlap,
+
+# Will be replaced by pathlib in 3.10
+[mypy-pathlib2.*]
+ignore_missing_imports = True
+
+# jaraco/jaraco.develop#20
+# Lucretiel/autocommand#38
+[mypy-autocommand.*]
+ignore_missing_imports = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/pyproject.toml 
new/jaraco_text-4.2.0/pyproject.toml
--- old/jaraco_text-4.0.0/pyproject.toml        2024-07-26 20:08:11.000000000 
+0200
+++ new/jaraco_text-4.2.0/pyproject.toml        2026-02-10 00:29:39.000000000 
+0100
@@ -1,5 +1,10 @@
 [build-system]
-requires = ["setuptools>=61.2", "setuptools_scm[toml]>=3.4.1"]
+requires = [
+       "setuptools>=77",
+       "setuptools_scm[toml]>=3.4.1",
+       # jaraco/skeleton#174
+       "coherent.licensed",
+]
 build-backend = "setuptools.build_meta"
 
 [project]
@@ -12,17 +17,16 @@
 classifiers = [
        "Development Status :: 5 - Production/Stable",
        "Intended Audience :: Developers",
-       "License :: OSI Approved :: MIT License",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3 :: Only",
 ]
-requires-python = ">=3.8"
+requires-python = ">=3.9"
+license = "MIT"
 dependencies = [
-       "jaraco.functools",
        "jaraco.context >= 4.1",
-       'importlib_resources; python_version < "3.9"',
-       "autocommand",
+       "jaraco.functools",
        "more_itertools",
+       "typer-slim",
 ]
 dynamic = ["version"]
 
@@ -33,15 +37,11 @@
 test = [
        # upstream
        "pytest >= 6, != 8.1.*",
-       "pytest-checkdocs >= 2.4",
-       "pytest-cov",
-       "pytest-mypy",
-       "pytest-enabler >= 2.2",
-       "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'",
 
        # local
        'pathlib2; python_version < "3.10"',
 ]
+
 doc = [
        # upstream
        "sphinx >= 3.5",
@@ -57,4 +57,28 @@
 ]
 inflect = ["inflect"]
 
+check = [
+       "pytest-checkdocs >= 2.4",
+       "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'",
+]
+
+cover = [
+       "pytest-cov",
+]
+
+enabler = [
+       "pytest-enabler >= 3.4",
+]
+
+type = [
+       # upstream
+       "pytest-mypy >= 1.0.1",
+
+       ## workaround for python/mypy#20454
+       "mypy < 1.19; python_implementation == 'PyPy'",
+
+       # local
+]
+
+
 [tool.setuptools_scm]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/ruff.toml 
new/jaraco_text-4.2.0/ruff.toml
--- old/jaraco_text-4.0.0/ruff.toml     2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/ruff.toml     2026-02-10 00:29:39.000000000 +0100
@@ -1,10 +1,31 @@
 [lint]
 extend-select = [
-       "C901",
-       "PERF401",
-       "W",
+       # upstream
+
+       "C901", # complex-structure
+       "I", # isort
+       "PERF401", # manual-list-comprehension
+
+       # Ensure modern type annotation syntax and best practices
+       # Not including those covered by type-checkers or exclusive to Python 
3.11+
+       "FA", # flake8-future-annotations
+       "F404", # late-future-import
+       "PYI", # flake8-pyi
+       "UP006", # non-pep585-annotation
+       "UP007", # non-pep604-annotation
+       "UP010", # unnecessary-future-import
+       "UP035", # deprecated-import
+       "UP037", # quoted-annotation
+       "UP043", # unnecessary-default-type-args
+
+       # local
 ]
 ignore = [
+       # upstream
+
+       # Typeshed rejects complex or non-literal defaults for maintenance and 
testing reasons,
+       # irrelevant to this project.
+       "PYI011", # typed-argument-default-in-stub
        # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
        "W191",
        "E111",
@@ -18,8 +39,8 @@
        "Q003",
        "COM812",
        "COM819",
-       "ISC001",
-       "ISC002",
+
+       # local
 ]
 
 [format]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/tests/test_core.py 
new/jaraco_text-4.2.0/tests/test_core.py
--- old/jaraco_text-4.0.0/tests/test_core.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/jaraco_text-4.2.0/tests/test_core.py    2026-02-10 00:29:39.000000000 
+0100
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+import pytest
+
+from jaraco.text import join_continuation
+
+
+def test_join_continuation_typing() -> None:
+    """
+    Ensures that the type annotation for join_continuation matches what we 
expect at runtime,
+    since checkers seem to give a false-positive on supporting ``__iter__`` 
and ``__next__``.
+    """
+
+    good: list[str] = ['foo \\', 'bar', 'baz']
+    expected: list[str] = ['foobar', 'baz']
+
+    # Ensure consistency at runtime for different iterables and no type error
+    assert list(join_continuation(good)) == expected
+    assert list(join_continuation(tuple(good))) == expected
+    assert list(join_continuation(map(lambda string: string, good))) == 
expected
+    assert list(join_continuation(iter(good))) == expected
+    assert list(join_continuation({string: None for string in good})) == 
expected
+
+    # NOTE: str isn't an expected use-case
+    # but unfortunately a str is an iterable of str, so we can't validate
+
+    # iter supports SupportsGetItem[int, str], but we don't !
+    bad: dict[int, str] = {i: string for (i, string) in enumerate(good)}
+    with pytest.raises(AttributeError):
+        list(join_continuation(bad))  # type: ignore[arg-type] # Testing for 
type error here!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/towncrier.toml 
new/jaraco_text-4.2.0/towncrier.toml
--- old/jaraco_text-4.0.0/towncrier.toml        2024-07-26 20:08:11.000000000 
+0200
+++ new/jaraco_text-4.2.0/towncrier.toml        2026-02-10 00:29:39.000000000 
+0100
@@ -1,2 +1,3 @@
 [tool.towncrier]
 title_format = "{version}"
+directory = "newsfragments"  # jaraco/skeleton#184
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco_text-4.0.0/tox.ini 
new/jaraco_text-4.2.0/tox.ini
--- old/jaraco_text-4.0.0/tox.ini       2024-07-26 20:08:11.000000000 +0200
+++ new/jaraco_text-4.2.0/tox.ini       2026-02-10 00:29:39.000000000 +0100
@@ -8,6 +8,10 @@
 usedevelop = True
 extras =
        test
+       check
+       cover
+       enabler
+       type
        inflect
 
 [testenv:diffcov]
@@ -28,9 +32,7 @@
 changedir = docs
 commands =
        python -m sphinx -W --keep-going . {toxinidir}/build/html
-       python -m sphinxlint \
-               # workaround for sphinx-contrib/sphinx-lint#83
-               --jobs 1
+       python -m sphinxlint
 
 [testenv:finalize]
 description = assemble changelog and tag a release

Reply via email to