From 9176e1f6bb62405ca489db511c41e975aa40ac30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Thu, 27 Aug 2026 08:16:20 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20hook=20parse=5Fintermixed?= =?UTF-8?q?=5Fargs=20as=20well?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since Python 3.12 parse_known_intermixed_args calls _parse_known_args2 directly, so a factory ending in parse_intermixed_args() skipped the :hook: monkeypatch, parsed Sphinx's argv and exited the build. Patch parse_known_intermixed_args alongside parse_known_args, through one patch.object context manager that restores both on any exception. --- CHANGELOG.md | 1 + roots/test-hook-intermixed/conf.py | 8 ++++++++ roots/test-hook-intermixed/index.rst | 4 ++++ roots/test-hook-intermixed/parser.py | 10 ++++++++++ src/sphinx_argparse_cli/_logic.py | 17 +++++++++-------- tests/test_logic.py | 18 ++++++++++++++++++ 6 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 roots/test-hook-intermixed/conf.py create mode 100644 roots/test-hook-intermixed/index.rst create mode 100644 roots/test-hook-intermixed/parser.py diff --git a/CHANGELOG.md b/CHANGELOG.md index df85f2b..a9f7aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Register flags and positional arguments as Sphinx program options so the `:option:` role links to them. - Leave apostrophes inside words (`don't`, `it's`) alone in help text instead of turning them into broken inline literals. +- Make `:hook:` intercept `parse_intermixed_args()` as well as `parse_args()`. ## 1.13.1 diff --git a/roots/test-hook-intermixed/conf.py b/roots/test-hook-intermixed/conf.py new file mode 100644 index 0000000..9f2a54a --- /dev/null +++ b/roots/test-hook-intermixed/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-hook-intermixed/index.rst b/roots/test-hook-intermixed/index.rst new file mode 100644 index 0000000..5a80656 --- /dev/null +++ b/roots/test-hook-intermixed/index.rst @@ -0,0 +1,4 @@ +.. sphinx_argparse_cli:: + :module: parser + :func: main + :hook: diff --git a/roots/test-hook-intermixed/parser.py b/roots/test-hook-intermixed/parser.py new file mode 100644 index 0000000..3097bdb --- /dev/null +++ b/roots/test-hook-intermixed/parser.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from argparse import ArgumentParser + + +def main() -> None: + parser = ArgumentParser(prog="foo", add_help=False) + parser.add_argument("--flag", help="a flag") + args = parser.parse_intermixed_args() + print(args) # noqa: T201 diff --git a/src/sphinx_argparse_cli/_logic.py b/src/sphinx_argparse_cli/_logic.py index c655bb7..cb34051 100644 --- a/src/sphinx_argparse_cli/_logic.py +++ b/src/sphinx_argparse_cli/_logic.py @@ -94,14 +94,15 @@ def parser(self) -> ArgumentParser: raise self.error(msg) # noqa: B904 parser: ArgumentParser | None = None if "hook" in self.options: - original_parse_known_args = ArgumentParser.parse_known_args - ArgumentParser.parse_known_args = _parse_known_args_hook # type: ignore[method-assign,assignment] - try: - parser_creator() - except HookError as hooked: - parser = hooked.parser - finally: - ArgumentParser.parse_known_args = original_parse_known_args + # parse_intermixed_args bypasses parse_known_args since Python 3.12, so both entry points need the hook + with ( + patch.object(ArgumentParser, "parse_known_args", _parse_known_args_hook), + patch.object(ArgumentParser, "parse_known_intermixed_args", _parse_known_args_hook), + ): + try: + parser_creator() + except HookError as hooked: + parser = hooked.parser else: parser = parser_creator() diff --git a/tests/test_logic.py b/tests/test_logic.py index 8bd6d6b..fb61838 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -70,6 +70,24 @@ def test_hook(build_outcome: str) -> None: assert build_outcome +@pytest.mark.sphinx(buildername="text", testroot="hook-intermixed") +def test_hook_intermixed(build_outcome: str) -> None: + assert ( + build_outcome + == """foo - CLI interface +******************* + + foo [--flag FLAG] + + +foo options +=========== + +* **"--flag"** "FLAG" - a flag +""" + ) + + @pytest.mark.sphinx(buildername="text", testroot="hook-fail") def test_hook_fail(app: SphinxTestApp, warning: StringIO) -> None: app.build()