From e470565af824b3c3323999a4553feabc8d223a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Sun, 1 Mar 2026 19:58:08 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(prog):=20propagate=20:prog:?= =?UTF-8?q?=20override=20to=20subcommands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using the :prog: directive option to rename the program, only the top-level parser prog was updated. Subcommand parsers still retained the original prog, producing inconsistent titles and references. Recursively updates all subparser prog values when :prog: is set. Fixes #71 --- roots/test-prog-subcommands/conf.py | 8 ++++++++ roots/test-prog-subcommands/index.rst | 4 ++++ roots/test-prog-subcommands/parser.py | 10 ++++++++++ src/sphinx_argparse_cli/_logic.py | 13 ++++++++++++- tests/test_logic.py | 7 +++++++ 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 roots/test-prog-subcommands/conf.py create mode 100644 roots/test-prog-subcommands/index.rst create mode 100644 roots/test-prog-subcommands/parser.py diff --git a/roots/test-prog-subcommands/conf.py b/roots/test-prog-subcommands/conf.py new file mode 100644 index 0000000..9f2a54a --- /dev/null +++ b/roots/test-prog-subcommands/conf.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +extensions = ["sphinx_argparse_cli"] +nitpicky = True diff --git a/roots/test-prog-subcommands/index.rst b/roots/test-prog-subcommands/index.rst new file mode 100644 index 0000000..9624f4b --- /dev/null +++ b/roots/test-prog-subcommands/index.rst @@ -0,0 +1,4 @@ +.. sphinx_argparse_cli:: + :module: parser + :func: make + :prog: my-tool diff --git a/roots/test-prog-subcommands/parser.py b/roots/test-prog-subcommands/parser.py new file mode 100644 index 0000000..7b03f89 --- /dev/null +++ b/roots/test-prog-subcommands/parser.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from argparse import ArgumentParser + + +def make() -> ArgumentParser: + parser = ArgumentParser(prog="original-name") + sub = parser.add_subparsers() + sub.add_parser("foo", help="foo help") + return parser diff --git a/src/sphinx_argparse_cli/_logic.py b/src/sphinx_argparse_cli/_logic.py index 51f0758..9e37ca1 100644 --- a/src/sphinx_argparse_cli/_logic.py +++ b/src/sphinx_argparse_cli/_logic.py @@ -140,7 +140,9 @@ def parser(self) -> ArgumentParser: raise self.error(msg) if "prog" in self.options: - self._parser.prog = self.options["prog"] + old_prog, new_prog = self._parser.prog, self.options["prog"] + self._parser.prog = new_prog + _update_sub_parser_prog(self._parser, old_prog, new_prog) self._raw_format = self._parser.formatter_class == RawDescriptionHelpFormatter return self._parser @@ -453,6 +455,15 @@ def _strip_ansi_colors(text: str) -> str: # pragma: >=3.14 cover return _ANSI_COLOR_RE.sub("", text) +def _update_sub_parser_prog(parser: ArgumentParser, old_prog: str, new_prog: str) -> None: + if not (sub_parsers := parser._subparsers): # noqa: SLF001 + return + sub_action: _SubParsersAction[ArgumentParser] = sub_parsers._group_actions[0] # type: ignore[assignment] # noqa: SLF001 + for sub_parser in sub_action.choices.values(): + sub_parser.prog = sub_parser.prog.replace(old_prog, new_prog, 1) + _update_sub_parser_prog(sub_parser, old_prog, new_prog) + + __all__ = [ "SphinxArgparseCli", ] diff --git a/tests/test_logic.py b/tests/test_logic.py index 4cdb210..2e6802c 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -390,3 +390,10 @@ def test_tuple_metavar(build_outcome: str) -> None: assert "select a pair" in build_outcome assert "default: None" not in build_outcome assert '"VAL"' in build_outcome + + +@pytest.mark.sphinx(buildername="text", testroot="prog-subcommands") +def test_prog_subcommands(build_outcome: str) -> None: + assert "my-tool" in build_outcome + assert "original-name" not in build_outcome + assert "my-tool foo" in build_outcome