Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-cliff for openSUSE:Factory 
checked in at 2026-05-18 17:46:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cliff (Old)
 and      /work/SRC/openSUSE:Factory/.python-cliff.new.1966 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-cliff"

Mon May 18 17:46:53 2026 rev:52 rq:1353615 version:4.14.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cliff/python-cliff.changes        
2026-04-11 22:29:39.308441966 +0200
+++ /work/SRC/openSUSE:Factory/.python-cliff.new.1966/python-cliff.changes      
2026-05-18 17:47:09.110158202 +0200
@@ -1,0 +2,16 @@
+Sun May 17 19:05:11 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.14.0:
+  * Fix command name alignment broken by ANSI color codes in help
+    output
+  * typing: Remove additional use of private _argparse module
+  * typing: Use objects from typing
+  * trivial: Use objects from collections.abc
+  * typing: Broaden return type of Command.get_parser
+  * ruff: Configure hacking as external linter
+  * tests: Drop use of testscenarios
+  * Relax help usage assertion for module-based runners
+  * Fix compatibility with cmd2 3.1.0+
+  * Fix cliff sphinxextention for Python 3.14
+
+-------------------------------------------------------------------

Old:
----
  cliff-4.13.3.tar.gz

New:
----
  cliff-4.14.0.tar.gz

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

Other differences:
------------------
++++++ python-cliff.spec ++++++
--- /var/tmp/diff_new_pack.O245Vg/_old  2026-05-18 17:47:09.754184814 +0200
+++ /var/tmp/diff_new_pack.O245Vg/_new  2026-05-18 17:47:09.754184814 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-cliff
-Version:        4.13.3
+Version:        4.14.0
 Release:        0
 Summary:        Command Line Interface Formulation Framework
 License:        Apache-2.0
@@ -33,7 +33,7 @@
 BuildRequires:  %{python_module PrettyTable >= 0.7.2}
 BuildRequires:  %{python_module PyYAML >= 3.12}
 BuildRequires:  %{python_module Sphinx >= 5.0.0}
-BuildRequires:  %{python_module cmd2 >= 1.0.0}
+BuildRequires:  %{python_module cmd2 >= 3.0.0}
 BuildRequires:  %{python_module coverage >= 5.0}
 BuildRequires:  %{python_module fixtures >= 3.0.0}
 BuildRequires:  %{python_module pytest}
@@ -45,7 +45,7 @@
 Requires:       python-PrettyTable >= 0.7.2
 Requires:       python-PyYAML >= 3.12
 Requires:       python-autopage >= 0.4.0
-Requires:       python-cmd2 >= 1.0.0
+Requires:       python-cmd2 >= 3.0.0
 Requires:       python-stevedore >= 5.6.0
 BuildArch:      noarch
 %python_subpackages
@@ -69,7 +69,7 @@
 %pytest cliff/tests
 
 %files %{python_files}
-%doc AUTHORS ChangeLog README.rst
+%doc ChangeLog README.rst
 %license LICENSE
 %{python_sitelib}/cliff
 %{python_sitelib}/cliff-%{version}.dist-info

++++++ cliff-4.13.3.tar.gz -> cliff-4.14.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/AUTHORS new/cliff-4.14.0/AUTHORS
--- old/cliff-4.13.3/AUTHORS    2026-04-08 11:09:59.000000000 +0200
+++ new/cliff-4.14.0/AUTHORS    2026-05-13 11:00:23.000000000 +0200
@@ -87,6 +87,7 @@
 Thomas Bechtold <[email protected]>
 Thomas Goirand <[email protected]>
 Thomas Herve <[email protected]>
+Todd Stansell <[email protected]>
 Tomaz Muraus <[email protected]>
 Tony Breeds <[email protected]>
 Tony Xu <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/ChangeLog new/cliff-4.14.0/ChangeLog
--- old/cliff-4.13.3/ChangeLog  2026-04-08 11:09:59.000000000 +0200
+++ new/cliff-4.14.0/ChangeLog  2026-05-13 11:00:23.000000000 +0200
@@ -1,17 +1,19 @@
 CHANGES
 =======
 
-4.13.3
+4.14.0
 ------
 
-* STABLE-ONLY: Revert bump of cmd2
+* Fix command name alignment broken by ANSI color codes in help output
 * typing: Remove additional use of private \_argparse module
+* typing: Use objects from typing
+* trivial: Use objects from collections.abc
 * typing: Broaden return type of Command.get\_parser
+* ruff: Configure hacking as external linter
+* tests: Drop use of testscenarios
 * Relax help usage assertion for module-based runners
-* Fix cliff sphinxextention for Python 3.14
-* Update TOX\_CONSTRAINTS\_FILE for stable/2026.1
-* Update .gitreview for stable/2026.1
 * Fix compatibility with cmd2 3.1.0+
+* Fix cliff sphinxextention for Python 3.14
 
 4.13.2
 ------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/PKG-INFO new/cliff-4.14.0/PKG-INFO
--- old/cliff-4.13.3/PKG-INFO   2026-04-08 11:09:59.334140000 +0200
+++ new/cliff-4.14.0/PKG-INFO   2026-05-13 11:00:23.282622600 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: cliff
-Version: 4.13.3
+Version: 4.14.0
 Summary: Command Line Interface Formulation Framework
 Author-email: OpenStack <[email protected]>
 License: Apache-2.0
@@ -22,7 +22,7 @@
 Description-Content-Type: text/x-rst
 License-File: LICENSE
 Requires-Dist: autopage>=0.4.0
-Requires-Dist: cmd2>=1.0.0
+Requires-Dist: cmd2>=3.0.0
 Requires-Dist: PrettyTable>=0.7.2
 Requires-Dist: stevedore>=5.6.0
 Requires-Dist: PyYAML>=3.12
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/_argparse.py 
new/cliff-4.14.0/cliff/_argparse.py
--- old/cliff-4.13.3/cliff/_argparse.py 2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/_argparse.py 2026-05-13 10:59:55.000000000 +0200
@@ -13,8 +13,8 @@
 """Overrides of standard argparse behavior."""
 
 import argparse
-import collections.abc
-import typing as ty
+from collections.abc import Iterable
+from typing import Any
 import warnings
 
 import autopage.argparse
@@ -26,14 +26,14 @@
     # special conflict handler.
 
     def add_argument_group(
-        self, *args: ty.Any, **kwargs: ty.Any
+        self, *args: Any, **kwargs: Any
     ) -> '_ArgumentGroup':
         group = _ArgumentGroup(self, *args, **kwargs)
         self._action_groups.append(group)
         return group
 
     def add_mutually_exclusive_group(
-        self, **kwargs: ty.Any
+        self, **kwargs: Any
     ) -> '_MutuallyExclusiveGroup':
         group = _MutuallyExclusiveGroup(self, **kwargs)
         self._mutually_exclusive_groups.append(group)
@@ -42,9 +42,7 @@
     def _handle_conflict_ignore(
         self,
         action: argparse.Action,
-        conflicting_actions: collections.abc.Iterable[
-            tuple[str, argparse.Action]
-        ],
+        conflicting_actions: Iterable[tuple[str, argparse.Action]],
     ) -> None:
         _handle_conflict_ignore(
             self,
@@ -58,7 +56,7 @@
     container: argparse._ActionsContainer,
     option_string_actions: dict[str, argparse.Action],
     new_action: argparse.Action,
-    conflicting_actions: collections.abc.Iterable[tuple[str, argparse.Action]],
+    conflicting_actions: Iterable[tuple[str, argparse.Action]],
 ) -> None:
     # Remember the option strings the new action starts with so we can
     # restore them as part of error reporting if we need to.
@@ -91,14 +89,14 @@
     # special conflict handler.
 
     def add_argument_group(
-        self, *args: ty.Any, **kwargs: ty.Any
+        self, *args: Any, **kwargs: Any
     ) -> '_ArgumentGroup':
         group = _ArgumentGroup(self, *args, **kwargs)
         self._action_groups.append(group)
         return group
 
     def add_mutually_exclusive_group(
-        self, **kwargs: ty.Any
+        self, **kwargs: Any
     ) -> '_MutuallyExclusiveGroup':
         group = _MutuallyExclusiveGroup(self, **kwargs)
         self._mutually_exclusive_groups.append(group)
@@ -107,9 +105,7 @@
     def _handle_conflict_ignore(
         self,
         action: argparse.Action,
-        conflicting_actions: collections.abc.Iterable[
-            tuple[str, argparse.Action]
-        ],
+        conflicting_actions: Iterable[tuple[str, argparse.Action]],
     ) -> None:
         _handle_conflict_ignore(
             self,
@@ -125,14 +121,14 @@
     # special conflict handler.
 
     def add_argument_group(
-        self, *args: ty.Any, **kwargs: ty.Any
+        self, *args: Any, **kwargs: Any
     ) -> '_ArgumentGroup':
         group = _ArgumentGroup(self, *args, **kwargs)
         self._action_groups.append(group)
         return group
 
     def add_mutually_exclusive_group(
-        self, **kwargs: ty.Any
+        self, **kwargs: Any
     ) -> '_MutuallyExclusiveGroup':
         group = _MutuallyExclusiveGroup(self, **kwargs)
         self._mutually_exclusive_groups.append(group)
@@ -141,9 +137,7 @@
     def _handle_conflict_ignore(
         self,
         action: argparse.Action,
-        conflicting_actions: collections.abc.Iterable[
-            tuple[str, argparse.Action]
-        ],
+        conflicting_actions: Iterable[tuple[str, argparse.Action]],
     ) -> None:
         _handle_conflict_ignore(
             self,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/app.py 
new/cliff-4.14.0/cliff/app.py
--- old/cliff-4.13.3/cliff/app.py       2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/app.py       2026-05-13 10:59:55.000000000 +0200
@@ -19,14 +19,14 @@
 import logging.handlers
 import os
 import sys
-import typing as ty
+from typing import TYPE_CHECKING, Any, TextIO
 
 from cliff import _argparse
 from . import complete
 from . import help
 from . import utils
 
-if ty.TYPE_CHECKING:
+if TYPE_CHECKING:
     from . import command as _command
     from . import commandmanager as _commandmanager
     from . import interactive as _interactive
@@ -79,9 +79,9 @@
         description: str | None,
         version: str | None,
         command_manager: '_commandmanager.CommandManager',
-        stdin: ty.TextIO | None = None,
-        stdout: ty.TextIO | None = None,
-        stderr: ty.TextIO | None = None,
+        stdin: TextIO | None = None,
+        stdout: TextIO | None = None,
+        stderr: TextIO | None = None,
         interactive_app_factory: type['_interactive.InteractiveApp']
         | None = None,
         deferred_help: bool = False,
@@ -99,9 +99,9 @@
 
     def _set_streams(
         self,
-        stdin: ty.TextIO | None,
-        stdout: ty.TextIO | None,
-        stderr: ty.TextIO | None,
+        stdin: TextIO | None,
+        stdout: TextIO | None,
+        stderr: TextIO | None,
     ) -> None:
         try:
             locale.setlocale(locale.LC_ALL, '')
@@ -142,7 +142,7 @@
         self,
         description: str | None,
         version: str | None,
-        argparse_kwargs: dict[str, ty.Any] | None = None,
+        argparse_kwargs: dict[str, Any] | None = None,
     ) -> argparse.ArgumentParser:
         """Return an argparse option parser for this application.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/columns.py 
new/cliff-4.14.0/cliff/columns.py
--- old/cliff-4.13.3/cliff/columns.py   2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/columns.py   2026-05-13 10:59:55.000000000 +0200
@@ -12,12 +12,12 @@
 
 """Formattable column tools."""
 
-import typing as ty
+from typing import Generic, TypeVar
 
-_T = ty.TypeVar('_T')
+_T = TypeVar('_T')
 
 
-class FormattableColumn(ty.Generic[_T]):
+class FormattableColumn(Generic[_T]):
     def __init__(self, value: _T) -> None:
         self._value = value
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/command.py 
new/cliff-4.14.0/cliff/command.py
--- old/cliff-4.13.3/cliff/command.py   2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/command.py   2026-05-13 10:59:55.000000000 +0200
@@ -15,18 +15,18 @@
 from importlib.metadata import packages_distributions
 import inspect
 import types
-import typing as ty
+from typing import TYPE_CHECKING, Any, TypeVar
 
 from stevedore import extension
 
 from cliff import _argparse
 
-if ty.TYPE_CHECKING:
+if TYPE_CHECKING:
     from . import app as _app
     from . import hooks
     from typing_extensions import Never
 
-_T = ty.TypeVar('_T')
+_T = TypeVar('_T')
 _dists_by_mods = None
 
 
@@ -174,7 +174,7 @@
         return parser
 
     @abc.abstractmethod
-    def take_action(self, parsed_args: argparse.Namespace) -> ty.Any:
+    def take_action(self, parsed_args: argparse.Namespace) -> Any:
         """Override to do something useful.
 
         The returned value will be returned by the program.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/commandmanager.py 
new/cliff-4.14.0/cliff/commandmanager.py
--- old/cliff-4.13.3/cliff/commandmanager.py    2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/commandmanager.py    2026-05-13 10:59:55.000000000 
+0200
@@ -12,7 +12,7 @@
 
 """Discover and lookup command plugins."""
 
-import collections.abc
+from collections.abc import Iterable, Iterator
 import importlib.metadata
 import logging
 from typing import TypeAlias
@@ -83,7 +83,7 @@
         namespace: str | None = None,
         convert_underscores: bool = True,
         *,
-        ignored_modules: collections.abc.Iterable[str] | None = None,
+        ignored_modules: Iterable[str] | None = None,
     ) -> None:
         self.namespace = namespace
         self.convert_underscores = convert_underscores
@@ -101,7 +101,7 @@
 
     @staticmethod
     def _is_module_ignored(
-        module_name: str, ignored_modules: collections.abc.Iterable[str]
+        module_name: str, ignored_modules: Iterable[str]
     ) -> bool:
         # given module_name = 'foo.bar.baz:wow', we expect to match any of
         # the following ignores: foo.bar.baz:wow, foo.bar.baz, foo.bar, foo
@@ -175,7 +175,7 @@
 
     def __iter__(
         self,
-    ) -> collections.abc.Iterator[tuple[str, EntryPointT]]:
+    ) -> Iterator[tuple[str, EntryPointT]]:
         return iter(self.commands.items())
 
     def add_command(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/complete.py 
new/cliff-4.14.0/cliff/complete.py
--- old/cliff-4.13.3/cliff/complete.py  2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/complete.py  2026-05-13 10:59:55.000000000 +0200
@@ -15,13 +15,13 @@
 import abc
 import argparse
 import logging
-import typing as ty
+from typing import TYPE_CHECKING, Any, TextIO
 
 import stevedore
 
 from cliff import command as _command
 
-if ty.TYPE_CHECKING:
+if TYPE_CHECKING:
     from cliff import app
 
 
@@ -59,7 +59,7 @@
         return ' '.join(k for k in sorted(self._dictionary.keys()))
 
     def _get_data_recurse(
-        self, dictionary: dict[str, ty.Any], path: str
+        self, dictionary: dict[str, Any], path: str
     ) -> list[tuple[str, str]]:
         ray = []
         keys = sorted(dictionary.keys())
@@ -81,7 +81,7 @@
 class CompleteShellBase(metaclass=abc.ABCMeta):
     """base class for bash completion generation"""
 
-    def __init__(self, name: str, output: ty.TextIO) -> None:
+    def __init__(self, name: str, output: TextIO) -> None:
         self.name = str(name)
         self.output = output
 
@@ -107,7 +107,7 @@
 class CompleteNoCode(CompleteShellBase):
     """completion with no code"""
 
-    def __init__(self, name: str, output: ty.TextIO) -> None:
+    def __init__(self, name: str, output: TextIO) -> None:
         super().__init__(name, output)
 
     def get_header(self) -> str:
@@ -120,7 +120,7 @@
 class CompleteBash(CompleteShellBase):
     """completion for bash"""
 
-    def __init__(self, name: str, output: ty.TextIO) -> None:
+    def __init__(self, name: str, output: TextIO) -> None:
         super().__init__(name, output)
 
     def get_header(self) -> str:
@@ -232,7 +232,7 @@
         return cmd_parser._get_optional_actions()
 
     def take_action(self, parsed_args: argparse.Namespace) -> int:
-        self.log.debug(f'take_action({parsed_args})')
+        self.log.debug('take_action(%s)', parsed_args)
 
         name = parsed_args.name or self.app.NAME
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/display.py 
new/cliff-4.14.0/cliff/display.py
--- old/cliff-4.13.3/cliff/display.py   2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/display.py   2026-05-13 10:59:55.000000000 +0200
@@ -14,9 +14,9 @@
 
 import abc
 import argparse
-import collections.abc
+from collections.abc import Iterable, Iterator, Sequence
 from itertools import compress
-import typing as ty
+from typing import Any, Generic, TypeVar
 
 import stevedore
 
@@ -24,12 +24,12 @@
 from cliff import command
 from cliff.formatters import base as base_formatters
 
-_T = ty.TypeVar("_T")
+_T = TypeVar("_T")
 
 
 class DisplayCommandBase(
     command.Command,
-    ty.Generic[base_formatters.FormatterT],
+    Generic[base_formatters.FormatterT],
     metaclass=abc.ABCMeta,
 ):
     """Command base class for displaying data about a single object."""
@@ -106,8 +106,8 @@
     def produce_output(
         self,
         parsed_args: argparse.Namespace,
-        column_names: collections.abc.Sequence[str],
-        data: ty.Any,
+        column_names: Sequence[str],
+        data: Any,
     ) -> int:
         """Use the formatter to generate the output.
 
@@ -121,7 +121,7 @@
     def _generate_columns_and_selector(
         self,
         parsed_args: argparse.Namespace,
-        column_names: collections.abc.Sequence[str],
+        column_names: Sequence[str],
     ) -> tuple[list[str], list[bool] | None]:
         """Generate included columns and selector according to parsed args.
 
@@ -167,12 +167,8 @@
     def _run_after_hooks(  # type: ignore[override]
         self,
         parsed_args: argparse.Namespace,
-        data: tuple[
-            collections.abc.Sequence[str], collections.abc.Iterable[ty.Any]
-        ],
-    ) -> tuple[
-        collections.abc.Sequence[str], collections.abc.Iterable[ty.Any]
-    ]:
+        data: tuple[Sequence[str], Iterable[Any]],
+    ) -> tuple[Sequence[str], Iterable[Any]]:
         """Calls after() method of the hooks.
 
         This method is intended to be called from the run() method after
@@ -196,7 +192,7 @@
 
     @staticmethod
     def _compress_iterable(
-        iterable: collections.abc.Iterable[_T],
-        selectors: collections.abc.Iterable[ty.Any],
-    ) -> collections.abc.Iterator[_T]:
+        iterable: Iterable[_T],
+        selectors: Iterable[Any],
+    ) -> Iterator[_T]:
         return compress(iterable, selectors)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/base.py 
new/cliff-4.14.0/cliff/formatters/base.py
--- old/cliff-4.13.3/cliff/formatters/base.py   2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/formatters/base.py   2026-05-13 10:59:55.000000000 
+0200
@@ -14,11 +14,11 @@
 
 import abc
 import argparse
-import collections.abc
-import typing as ty
+from collections.abc import Iterable, Sequence
+from typing import Any, TextIO, TypeVar
 
 
-FormatterT = ty.TypeVar('FormatterT', bound='Formatter')
+FormatterT = TypeVar('FormatterT', bound='Formatter')
 
 
 class Formatter(metaclass=abc.ABCMeta):
@@ -36,9 +36,9 @@
     @abc.abstractmethod
     def emit_list(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         """Format and print the list from the iterable data source.
@@ -63,9 +63,9 @@
     @abc.abstractmethod
     def emit_one(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Sequence[Any],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         """Format and print the values associated with the single object.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/commaseparated.py 
new/cliff-4.14.0/cliff/formatters/commaseparated.py
--- old/cliff-4.13.3/cliff/formatters/commaseparated.py 2026-04-08 
11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/formatters/commaseparated.py 2026-05-13 
10:59:55.000000000 +0200
@@ -13,17 +13,17 @@
 """Output formatters using csv format."""
 
 import argparse
-import collections.abc
+from collections.abc import Iterable, Sequence
 import csv
 import os
-import typing as ty
+from typing import Any, Literal, TextIO
 
 from cliff import columns
 from cliff.formatters import base
 
 
 class CSVLister(base.ListFormatter):
-    QUOTE_MODES: dict[str, ty.Literal[0, 1, 2, 3]] = {
+    QUOTE_MODES: dict[str, Literal[0, 1, 2, 3]] = {
         'all': csv.QUOTE_ALL,
         'minimal': csv.QUOTE_MINIMAL,
         'nonnumeric': csv.QUOTE_NONNUMERIC,
@@ -42,9 +42,9 @@
 
     def emit_list(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         writer = csv.writer(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/json_format.py 
new/cliff-4.14.0/cliff/formatters/json_format.py
--- old/cliff-4.13.3/cliff/formatters/json_format.py    2026-04-08 
11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/formatters/json_format.py    2026-05-13 
10:59:55.000000000 +0200
@@ -13,9 +13,9 @@
 """Output formatters for JSON."""
 
 import argparse
-import collections.abc
+from collections.abc import Iterable, Sequence
 import json
-import typing as ty
+from typing import Any, TextIO
 
 from cliff import columns
 from cliff.formatters import base
@@ -33,9 +33,9 @@
 
     def emit_list(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         items = []
@@ -56,9 +56,9 @@
 
     def emit_one(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Sequence[Any],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         one = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/shell.py 
new/cliff-4.14.0/cliff/formatters/shell.py
--- old/cliff-4.13.3/cliff/formatters/shell.py  2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/formatters/shell.py  2026-05-13 10:59:55.000000000 
+0200
@@ -13,8 +13,8 @@
 """Output formatters using shell syntax."""
 
 import argparse
-import collections.abc
-import typing as ty
+from collections.abc import Sequence
+from typing import Any, TextIO
 
 from cliff import columns
 from cliff.formatters import base
@@ -44,9 +44,9 @@
 
     def emit_one(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Sequence[Any],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         variable_names = [c.lower().replace(' ', '_') for c in column_names]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/table.py 
new/cliff-4.14.0/cliff/formatters/table.py
--- old/cliff-4.13.3/cliff/formatters/table.py  2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/formatters/table.py  2026-05-13 10:59:55.000000000 
+0200
@@ -13,10 +13,10 @@
 """Output formatters using prettytable."""
 
 import argparse
-import collections.abc
+from collections.abc import Iterable, Sequence
 import os
 import sys
-import typing as ty
+from typing import Any, Literal, TextIO, TypeVar
 
 import prettytable
 
@@ -24,13 +24,11 @@
 from cliff.formatters import base
 from cliff import utils
 
-_T = ty.TypeVar('_T')
+_T = TypeVar('_T')
 
 
 def _format_row(
-    row: collections.abc.Iterable[
-        columns.FormattableColumn[ty.Any] | str | _T
-    ],
+    row: Iterable[columns.FormattableColumn[Any] | str | _T],
 ) -> list[_T | str]:
     new_row = []
     for r in row:
@@ -51,7 +49,7 @@
 
 
 class TableFormatter(base.ListFormatter, base.SingleFormatter):
-    ALIGNMENTS: dict[type[int | str | float], ty.Literal['l', 'c', 'r']] = {
+    ALIGNMENTS: dict[type[int | str | float], Literal['l', 'c', 'r']] = {
         int: 'r',
         str: 'l',
         float: 'r',
@@ -90,8 +88,8 @@
     def add_rows(
         self,
         table: prettytable.PrettyTable,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
     ) -> None:
         # Figure out the types of the columns in the
         # first row and set the alignment of the
@@ -112,9 +110,9 @@
 
     def emit_list(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         x = prettytable.PrettyTable(
@@ -142,9 +140,9 @@
 
     def emit_one(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Sequence[Any],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         x = prettytable.PrettyTable(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/value.py 
new/cliff-4.14.0/cliff/formatters/value.py
--- old/cliff-4.13.3/cliff/formatters/value.py  2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/formatters/value.py  2026-05-13 10:59:55.000000000 
+0200
@@ -13,8 +13,8 @@
 """Output formatters values only"""
 
 import argparse
-import collections.abc
-import typing as ty
+from collections.abc import Iterable, Sequence
+from typing import Any, TextIO
 
 from cliff import columns
 from cliff.formatters import base
@@ -26,9 +26,9 @@
 
     def emit_list(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         for row in data:
@@ -47,9 +47,9 @@
 
     def emit_one(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Sequence[Any],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         for value in data:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/formatters/yaml_format.py 
new/cliff-4.14.0/cliff/formatters/yaml_format.py
--- old/cliff-4.13.3/cliff/formatters/yaml_format.py    2026-04-08 
11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/formatters/yaml_format.py    2026-05-13 
10:59:55.000000000 +0200
@@ -13,14 +13,14 @@
 """Output formatters using PyYAML."""
 
 import argparse
-import collections.abc
-import typing as ty
+from collections.abc import Iterable, Sequence
+from typing import Any, TextIO
 
 from cliff import columns
 from cliff.formatters import base
 
 
-def _yaml_friendly(value: ty.Any) -> ty.Any:
+def _yaml_friendly(value: Any) -> Any:
     if isinstance(value, columns.FormattableColumn):
         return value.machine_readable()
     elif hasattr(value, "toDict"):
@@ -37,9 +37,9 @@
 
     def emit_list(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         # the yaml import is slow, so defer loading until we know we want it
@@ -54,9 +54,9 @@
 
     def emit_one(
         self,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
-        stdout: ty.TextIO,
+        column_names: Sequence[str],
+        data: Sequence[Any],
+        stdout: TextIO,
         parsed_args: argparse.Namespace,
     ) -> None:
         # the yaml import is slow, so defer loading until we know we want it
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/help.py 
new/cliff-4.14.0/cliff/help.py
--- old/cliff-4.13.3/cliff/help.py      2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/help.py      2026-05-13 10:59:55.000000000 +0200
@@ -11,10 +11,10 @@
 #  under the License.
 
 import argparse
-import collections.abc
+from collections.abc import Sequence
 import inspect
 import traceback
-import typing as ty
+from typing import Any
 
 import autopage.argparse
 
@@ -43,7 +43,7 @@
         self,
         parser: argparse.ArgumentParser,
         namespace: argparse.Namespace,
-        values: str | collections.abc.Sequence[ty.Any] | None,
+        values: str | Sequence[Any] | None,
         option_string: str | None = None,
     ) -> None:
         app = self.default
@@ -94,9 +94,10 @@
                         dist_info = f'\033[90m{dist_info}\033[39m'
                 else:
                     dist_info = ''
+                padded_name = f'{name:<13}'
                 if color:
-                    name = f'\033[36m{name}\033[39m'
-                out.write(f'  {name:<13}  {one_liner}{dist_info}\n')
+                    padded_name = f'\033[36m{padded_name}\033[39m'
+                out.write(f'  {padded_name}  {one_liner}{dist_info}\n')
         raise HelpExit()
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/interactive.py 
new/cliff-4.14.0/cliff/interactive.py
--- old/cliff-4.13.3/cliff/interactive.py       2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/interactive.py       2026-05-13 10:59:55.000000000 
+0200
@@ -15,12 +15,12 @@
 import itertools
 import shlex
 import sys
-import typing as ty
+from typing import TYPE_CHECKING, TextIO
 
 import autopage.argparse
 import cmd2
 
-if ty.TYPE_CHECKING:
+if TYPE_CHECKING:
     from . import app as _app
     from . import commandmanager as _commandmanager
 
@@ -50,8 +50,8 @@
         self,
         parent_app: '_app.App',
         command_manager: '_commandmanager.CommandManager',
-        stdin: ty.TextIO | None,
-        stdout: ty.TextIO | None,
+        stdin: TextIO | None,
+        stdout: TextIO | None,
         errexit: bool = False,
     ):
         self.parent_app = parent_app
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/lister.py 
new/cliff-4.14.0/cliff/lister.py
--- old/cliff-4.13.3/cliff/lister.py    2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/lister.py    2026-05-13 10:59:55.000000000 +0200
@@ -14,9 +14,9 @@
 
 import abc
 import argparse
-import collections.abc
+from collections.abc import Iterable, Sequence
 import logging
-import typing as ty
+from typing import Any
 
 from cliff import display
 from cliff.formatters import base as base_formatters
@@ -50,9 +50,7 @@
     @abc.abstractmethod
     def take_action(
         self, parsed_args: argparse.Namespace
-    ) -> tuple[
-        collections.abc.Sequence[str], collections.abc.Iterable[ty.Any]
-    ]:
+    ) -> tuple[Sequence[str], Iterable[Any]]:
         """Run command.
 
         Return a tuple containing the column names and an iterable containing
@@ -94,8 +92,8 @@
     def produce_output(
         self,
         parsed_args: argparse.Namespace,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Iterable[collections.abc.Sequence[ty.Any]],
+        column_names: Sequence[str],
+        data: Iterable[Sequence[Any]],
     ) -> int:
         if parsed_args.sort_columns and self.need_sort_by_cliff:
             indexes = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/show.py 
new/cliff-4.14.0/cliff/show.py
--- old/cliff-4.13.3/cliff/show.py      2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/show.py      2026-05-13 10:59:55.000000000 +0200
@@ -14,8 +14,8 @@
 
 import abc
 import argparse
-import collections.abc
-import typing as ty
+from collections.abc import Iterable, Sequence
+from typing import Any
 
 from cliff import display
 from cliff.formatters import base as base_formatters
@@ -38,9 +38,7 @@
     @abc.abstractmethod
     def take_action(
         self, parsed_args: argparse.Namespace
-    ) -> tuple[
-        collections.abc.Sequence[str], collections.abc.Iterable[ty.Any]
-    ]:
+    ) -> tuple[Sequence[str], Iterable[Any]]:
         """Run command.
 
         Return a tuple containing the column names and an iterable containing
@@ -50,8 +48,8 @@
     def produce_output(
         self,
         parsed_args: argparse.Namespace,
-        column_names: collections.abc.Sequence[str],
-        data: collections.abc.Sequence[ty.Any],
+        column_names: Sequence[str],
+        data: Sequence[Any],
     ) -> int:
         columns_to_include, selector = self._generate_columns_and_selector(
             parsed_args, column_names
@@ -64,8 +62,8 @@
         return 0
 
     def dict2columns(
-        self, data: dict[str, ty.Any]
-    ) -> tuple[tuple[str, ...], tuple[ty.Any, ...]]:
+        self, data: dict[str, Any]
+    ) -> tuple[tuple[str, ...], tuple[Any, ...]]:
         """Implement the common task of converting a dict-based object
         to the two-column output that ShowOne expects.
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/sphinxext.py 
new/cliff-4.14.0/cliff/sphinxext.py
--- old/cliff-4.13.3/cliff/sphinxext.py 2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/sphinxext.py 2026-05-13 10:59:55.000000000 +0200
@@ -13,13 +13,13 @@
 # under the License.
 
 import argparse
-import collections.abc
+from collections.abc import Iterable
 import fnmatch
 import importlib
 import inspect
 import re
 import sys
-import typing as ty
+from typing import Any
 
 from docutils import nodes
 from docutils.parsers import rst
@@ -36,7 +36,7 @@
     """Indent by four spaces."""
     prefix = ' ' * 4
 
-    def prefixed_lines() -> collections.abc.Iterable[str]:
+    def prefixed_lines() -> Iterable[str]:
         for line in text.splitlines(True):
             yield (prefix + line if line.strip() else line)
 
@@ -45,7 +45,7 @@
 
 def _format_description(
     parser: argparse.ArgumentParser,
-) -> collections.abc.Iterable[str]:
+) -> Iterable[str]:
     """Get parser description.
 
     We parse this as reStructuredText, allowing users to embed rich
@@ -110,7 +110,7 @@
 
 def _format_epilog(
     parser: argparse.ArgumentParser,
-) -> collections.abc.Iterable[str]:
+) -> Iterable[str]:
     """Get parser epilog.
 
     We parse this as reStructuredText, allowing users to embed rich
@@ -127,7 +127,7 @@
 
 def _format_positional_action(
     action: argparse.Action,
-) -> collections.abc.Iterable[str]:
+) -> Iterable[str]:
     """Format a positional action."""
     if action.help == argparse.SUPPRESS:
         return
@@ -156,7 +156,7 @@
 
 def _format_optional_action(
     action: argparse.Action,
-) -> collections.abc.Iterable[str]:
+) -> Iterable[str]:
     """Format an optional action."""
     if action.help == argparse.SUPPRESS or action.option_strings is None:
         return
@@ -191,7 +191,7 @@
 
 def _format_parser(
     parser: argparse.ArgumentParser,
-) -> collections.abc.Iterable[str]:
+) -> Iterable[str]:
     """Format the output of an argparse 'ArgumentParser' object.
 
     Given the following parser::
@@ -465,7 +465,7 @@
         return self._generate_command_nodes(commands, application_name)
 
 
-def setup(app: sphinx.application.Sphinx) -> dict[str, ty.Any]:
+def setup(app: sphinx.application.Sphinx) -> dict[str, Any]:
     app.add_directive('autoprogram-cliff', AutoprogramCliffDirective)
     app.add_config_value('autoprogram_cliff_application', '', 'env')
     app.add_config_value('autoprogram_cliff_ignored', ['--help'], 'env')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/tests/test_commandmanager.py 
new/cliff-4.14.0/cliff/tests/test_commandmanager.py
--- old/cliff-4.13.3/cliff/tests/test_commandmanager.py 2026-04-08 
11:09:14.000000000 +0200
+++ new/cliff-4.14.0/cliff/tests/test_commandmanager.py 2026-05-13 
10:59:55.000000000 +0200
@@ -12,68 +12,63 @@
 
 from unittest import mock
 
-import testscenarios  # type: ignore
-
 from cliff import command
 from cliff import commandmanager
 from cliff.tests import base
 from cliff.tests import utils
 
 
-load_tests = testscenarios.load_tests_apply_scenarios
-
-
 class TestLookupAndFind(base.TestBase):
     scenarios = [
-        ('one-word', {'argv': ['one']}),
-        ('two-words', {'argv': ['two', 'words']}),
-        ('three-words', {'argv': ['three', 'word', 'command']}),
+        ('one-word', ['one']),
+        ('two-words', ['two', 'words']),
+        ('three-words', ['three', 'word', 'command']),
     ]
 
-    argv: list[str]
-
     def test(self):
-        mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
-        cmd, name, remaining = mgr.find_command(self.argv)
-        self.assertTrue(cmd)
-        self.assertEqual(' '.join(self.argv), name)
-        self.assertFalse(remaining)
+        for scenario, argv in self.scenarios:
+            with self.subTest(scenario, argv=argv):
+                mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
+                cmd, name, remaining = mgr.find_command(argv)
+                self.assertTrue(cmd)
+                self.assertEqual(' '.join(argv), name)
+                self.assertFalse(remaining)
 
 
 class TestLookupWithRemainder(base.TestBase):
     scenarios = [
-        ('one', {'argv': ['one', '--opt']}),
-        ('two', {'argv': ['two', 'words', '--opt']}),
-        ('three', {'argv': ['three', 'word', 'command', '--opt']}),
+        ('one', ['one', '--opt']),
+        ('two', ['two', 'words', '--opt']),
+        ('three', ['three', 'word', 'command', '--opt']),
     ]
 
-    argv: list[str]
-
     def test(self):
-        mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
-        cmd, name, remaining = mgr.find_command(self.argv)
-        self.assertTrue(cmd)
-        self.assertEqual(['--opt'], remaining)
+        for scenario, argv in self.scenarios:
+            with self.subTest(scenario, argv=argv):
+                mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
+                cmd, name, remaining = mgr.find_command(argv)
+                self.assertTrue(cmd)
+                self.assertEqual(['--opt'], remaining)
 
 
 class TestFindInvalidCommand(base.TestBase):
     scenarios = [
-        ('no-such-command', {'argv': ['a', '-b']}),
-        ('no-command-given', {'argv': ['-b']}),
+        ('no-such-command', ['a', '-b']),
+        ('no-command-given', ['-b']),
     ]
 
-    argv: list[str]
-
     def test(self):
-        mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
-        try:
-            mgr.find_command(self.argv)
-        except ValueError as err:
-            # make sure err include 'a' when ['a', '-b']
-            self.assertIn(self.argv[0], str(err))
-            self.assertIn('-b', str(err))
-        else:
-            self.fail('expected a failure')
+        for scenario, argv in self.scenarios:
+            with self.subTest(scenario, argv=argv):
+                mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
+                try:
+                    mgr.find_command(argv)
+                except ValueError as err:
+                    # make sure err include 'a' when ['a', '-b']
+                    self.assertIn(argv[0], str(err))
+                    self.assertIn('-b', str(err))
+                else:
+                    self.fail('expected a failure')
 
 
 class TestFindUnknownCommand(base.TestBase):
@@ -247,19 +242,19 @@
 
 class TestLookupAndFindPartialName(base.TestBase):
     scenarios = [
-        ('one-word', {'argv': ['o']}),
-        ('two-words', {'argv': ['t', 'w']}),
-        ('three-words', {'argv': ['t', 'w', 'c']}),
+        ('one-word', ['o']),
+        ('two-words', ['t', 'w']),
+        ('three-words', ['t', 'w', 'c']),
     ]
 
-    argv: list[str]
-
     def test(self):
-        mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
-        cmd, name, remaining = mgr.find_command(self.argv)
-        self.assertTrue(cmd)
-        self.assertEqual(' '.join(self.argv), name)
-        self.assertFalse(remaining)
+        for scenario, argv in self.scenarios:
+            with self.subTest(scenario, argv=argv):
+                mgr = utils.TestCommandManager(utils.TEST_NAMESPACE)
+                cmd, name, remaining = mgr.find_command(argv)
+                self.assertTrue(cmd)
+                self.assertEqual(' '.join(argv), name)
+                self.assertFalse(remaining)
 
 
 class TestIsModuleIgnored(base.TestBase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/tests/test_help.py 
new/cliff-4.14.0/cliff/tests/test_help.py
--- old/cliff-4.13.3/cliff/tests/test_help.py   2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/tests/test_help.py   2026-05-13 10:59:55.000000000 
+0200
@@ -11,6 +11,7 @@
 #  under the License.
 
 import io
+import re
 
 from unittest import mock
 
@@ -194,3 +195,36 @@
         self.assertIn('Commands:', help_output)
         self.assertIn('Could not load', help_output)
         self.assertIn('Exception: Could not load EntryPoint', help_output)
+
+    def test_show_help_color_output_equivalent_to_plain(self):
+        """Colored help should be identical save for ANSI codes."""
+
+        def run_help(color):
+            output = io.StringIO()
+            pager = mock.MagicMock()
+            pager.to_terminal.return_value = color
+            pager.__enter__ = mock.Mock(return_value=output)
+            pager.__exit__ = mock.Mock(return_value=False)
+            app = application.App(
+                'testing',
+                '1',
+                utils.TestCommandManager(utils.TEST_NAMESPACE),
+                stdout=io.StringIO(),
+            )
+            app.NAME = 'test'
+            with mock.patch(
+                'cliff.help.autopage.argparse.help_pager', return_value=pager
+            ):
+                with mock.patch(
+                    'cliff.help.autopage.argparse.use_color_for_parser'
+                ):
+                    try:
+                        app.run(['--help'])
+                    except help.HelpExit:
+                        pass
+            return output.getvalue()
+
+        plain = run_help(color=False)
+        colored = run_help(color=True)
+        self.assertNotEqual(plain, colored)
+        self.assertEqual(plain, re.sub(r'\x1b\[[0-9;]*m', '', colored))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff/tests/test_lister.py 
new/cliff-4.14.0/cliff/tests/test_lister.py
--- old/cliff-4.13.3/cliff/tests/test_lister.py 2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/cliff/tests/test_lister.py 2026-05-13 10:59:55.000000000 
+0200
@@ -11,7 +11,7 @@
 #  under the License.
 
 import argparse
-import typing as ty
+from typing import Any
 import weakref
 
 from unittest import mock
@@ -34,7 +34,7 @@
 
 
 class ExerciseLister(lister.Lister):
-    data: list[tuple[ty.Any, ty.Any]] = [('a', 'A'), ('b', 'B'), ('c', 'A')]
+    data: list[tuple[Any, Any]] = [('a', 'A'), ('b', 'B'), ('c', 'A')]
 
     def _load_formatter_plugins(self):
         return {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff.egg-info/PKG-INFO 
new/cliff-4.14.0/cliff.egg-info/PKG-INFO
--- old/cliff-4.13.3/cliff.egg-info/PKG-INFO    2026-04-08 11:09:59.000000000 
+0200
+++ new/cliff-4.14.0/cliff.egg-info/PKG-INFO    2026-05-13 11:00:23.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: cliff
-Version: 4.13.3
+Version: 4.14.0
 Summary: Command Line Interface Formulation Framework
 Author-email: OpenStack <[email protected]>
 License: Apache-2.0
@@ -22,7 +22,7 @@
 Description-Content-Type: text/x-rst
 License-File: LICENSE
 Requires-Dist: autopage>=0.4.0
-Requires-Dist: cmd2>=1.0.0
+Requires-Dist: cmd2>=3.0.0
 Requires-Dist: PrettyTable>=0.7.2
 Requires-Dist: stevedore>=5.6.0
 Requires-Dist: PyYAML>=3.12
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff.egg-info/pbr.json 
new/cliff-4.14.0/cliff.egg-info/pbr.json
--- old/cliff-4.13.3/cliff.egg-info/pbr.json    2026-04-08 11:09:59.000000000 
+0200
+++ new/cliff-4.14.0/cliff.egg-info/pbr.json    2026-05-13 11:00:23.000000000 
+0200
@@ -1 +1 @@
-{"git_version": "9f0d93b", "is_release": true}
\ No newline at end of file
+{"git_version": "9e12d09", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/cliff.egg-info/requires.txt 
new/cliff-4.14.0/cliff.egg-info/requires.txt
--- old/cliff-4.13.3/cliff.egg-info/requires.txt        2026-04-08 
11:09:59.000000000 +0200
+++ new/cliff-4.14.0/cliff.egg-info/requires.txt        2026-05-13 
11:00:23.000000000 +0200
@@ -1,5 +1,5 @@
 autopage>=0.4.0
-cmd2>=1.0.0
+cmd2>=3.0.0
 PrettyTable>=0.7.2
 stevedore>=5.6.0
 PyYAML>=3.12
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/pyproject.toml 
new/cliff-4.14.0/pyproject.toml
--- old/cliff-4.13.3/pyproject.toml     2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/pyproject.toml     2026-05-13 10:59:55.000000000 +0200
@@ -75,6 +75,7 @@
 show_column_numbers = true
 show_error_context = true
 strict = true
+disable_error_code = ["import-untyped"]
 exclude = "(?x)(doc | demoapp | releasenotes)"
 
 [[tool.mypy.overrides]]
@@ -91,7 +92,8 @@
 docstring-code-format = true
 
 [tool.ruff.lint]
-select = ["E4", "E5", "E7", "E9", "F", "S", "UP"]
+select = ["E4", "E5", "E7", "E9", "F", "G", "LOG", "S", "UP"]
+external = ["H"]
 
 [tool.ruff.lint.per-file-ignores]
 "cliff/tests/*" = ["S"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/requirements.txt 
new/cliff-4.14.0/requirements.txt
--- old/cliff-4.13.3/requirements.txt   2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/requirements.txt   2026-05-13 10:59:55.000000000 +0200
@@ -1,5 +1,5 @@
 autopage>=0.4.0 # Apache 2.0
-cmd2>=1.0.0 # MIT
+cmd2>=3.0.0 # MIT
 PrettyTable>=0.7.2 # BSD
 stevedore>=5.6.0 # Apache-2.0
 PyYAML>=3.12 # MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/test-requirements.txt 
new/cliff-4.14.0/test-requirements.txt
--- old/cliff-4.13.3/test-requirements.txt      2026-04-08 11:09:14.000000000 
+0200
+++ new/cliff-4.14.0/test-requirements.txt      2026-05-13 10:59:55.000000000 
+0200
@@ -1,10 +1,6 @@
-stestr>=1.0.0 # Apache-2.0
-testtools>=2.2.0 # MIT
-testscenarios>=0.4 # Apache-2.0/BSD
 fixtures>=3.0.0 # Apache-2.0/BSD
-
-coverage>=5.0 # Apache-2.0
-
 # sphinx is required in test-requirements in addition to doc/requirements
 # because there is a sphinx extension that has tests
 sphinx>=5.0.0 # BSD
+stestr>=1.0.0 # Apache-2.0
+testtools>=2.2.0 # MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cliff-4.13.3/tox.ini new/cliff-4.14.0/tox.ini
--- old/cliff-4.13.3/tox.ini    2026-04-08 11:09:14.000000000 +0200
+++ new/cliff-4.14.0/tox.ini    2026-05-13 10:59:55.000000000 +0200
@@ -12,7 +12,7 @@
   stestr run {posargs}
   stestr slowest
 deps =
-  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2026.1}
+  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/test-requirements.txt
   -r{toxinidir}/requirements.txt
 
@@ -55,11 +55,14 @@
 
 [testenv:docs]
 deps =
-  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2026.1}
+  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/doc/requirements.txt
 commands = sphinx-build -W -b html doc/source doc/build/html
 
 [testenv:cover]
+deps =
+  {[testenv]deps}
+  coverage>=5.0
 setenv =
   {[testenv]setenv}
   PYTHON=coverage run --source cliff --parallel-mode
@@ -80,3 +83,8 @@
 import-order-style = pep8
 show-source = true
 exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build
+
+[hacking]
+import_exceptions =
+  collections.abc
+  typing

Reply via email to