From 976e4ac6c271dad69a23f87d1a23bca4b554330d Mon Sep 17 00:00:00 2001 From: Robert Roos Date: Mon, 26 Jan 2026 09:04:32 +0100 Subject: [PATCH 1/4] Changed filename field to be explicit --- docs/changelog.rst | 7 +++++++ sphinxarg/ext.py | 26 +++++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8df9f41..60d8fc7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,13 @@ Change log ********** +x.x.x +##### + +Changes + +* The ``:filename:`` input is now resolved only to the ``conf.py`` directory (``srcdir`` in Sphinx) + 0.6.0 ##### diff --git a/sphinxarg/ext.py b/sphinxarg/ext.py index 702d455..af58dd0 100644 --- a/sphinxarg/ext.py +++ b/sphinxarg/ext.py @@ -493,23 +493,23 @@ def _nested_parse_paragraph(self, text): return content def _open_filename(self): + file = self.options['filename'] + + # If the provided path is not absolute, we consider it relative to the docs + # conf dir: + if not os.path.isabs(file): + file = os.path.join(self.env.srcdir, file) + # try open with given path try: - return open(self.options['filename']) - except OSError: - pass - # try open with abspath - try: - return open(os.path.abspath(self.options['filename'])) + return open(file) except OSError: pass - # try open with shutil which - try: - return open(shutil.which(self.options['filename'])) - except (OSError, TypeError): - pass - # raise exception - raise FileNotFoundError(self.options['filename']) + + raise FileNotFoundError( + f"Failed to find provided source file `{self.options['filename']}` " + f"(resolved to `{file}`)" + ) def _print_subcommands(self, data, nested_content, markdown_help=False, settings=None): """ From 3a5d9a942970ba9ba0cf21f4fbf1bab775af69f2 Mon Sep 17 00:00:00 2001 From: Robert Roos Date: Mon, 26 Jan 2026 09:59:40 +0100 Subject: [PATCH 2/4] Fixed tests through mock --- sphinxarg/ext.py | 20 ++++++++++++++----- test/conftest.py | 14 +++++++++++++ test/roots/test-argparse-directive/index.rst | 2 +- .../test-command-by-group-index/sample.rst | 2 +- .../subcommand-a.rst | 2 +- .../subcommand-b.rst | 2 +- test/roots/test-command-index/sample.rst | 2 +- .../roots/test-command-index/subcommand-a.rst | 2 +- .../roots/test-command-index/subcommand-b.rst | 2 +- test/roots/test-conf-opts-html/index.rst | 2 +- .../test-conf-opts-html/subcommand-a.rst | 2 +- .../test-default-html/default-suppressed.rst | 2 +- test/roots/test-default-html/index.rst | 2 +- .../test-default-html/special-characters.rst | 2 +- test/roots/test-default-html/subcommand-a.rst | 2 +- 15 files changed, 42 insertions(+), 18 deletions(-) diff --git a/sphinxarg/ext.py b/sphinxarg/ext.py index af58dd0..de05524 100644 --- a/sphinxarg/ext.py +++ b/sphinxarg/ext.py @@ -3,7 +3,6 @@ import importlib import operator import os -import shutil import sys from argparse import ArgumentParser from typing import TYPE_CHECKING, ClassVar, cast @@ -492,13 +491,23 @@ def _nested_parse_paragraph(self, text): self.state.nested_parse(StringList(text.split('\n')), 0, content) return content + @property + def _srcdir(self): + """Internal property for source root-dir. + + This is a property such that we can easily mock it during tests. + """ + return self.env.srcdir + def _open_filename(self): file = self.options['filename'] # If the provided path is not absolute, we consider it relative to the docs # conf dir: if not os.path.isabs(file): - file = os.path.join(self.env.srcdir, file) + file = os.path.realpath( + os.path.join(self._srcdir, file) + ) # Resolve things like ".." to make clear where we're searching # try open with given path try: @@ -506,10 +515,11 @@ def _open_filename(self): except OSError: pass - raise FileNotFoundError( - f"Failed to find provided source file `{self.options['filename']}` " - f"(resolved to `{file}`)" + msg = ( + f'Failed to find provided source file `{self.options["filename"]}` ' + f'(resolved to `{file}`)' ) + raise FileNotFoundError(msg) def _print_subcommands(self, data, nested_content, markdown_help=False, settings=None): """ diff --git a/test/conftest.py b/test/conftest.py index 1872ee3..a6382d9 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -7,11 +7,25 @@ from lxml import etree as lxmltree from sphinx.testing.util import SphinxTestApp +from sphinxarg.ext import ArgParseDirective + pytest_plugins = 'sphinx.testing.fixtures' etree_cache: dict[str, str] = {} +@pytest.fixture(autouse=True) +def mock_argparse_src_dir(monkeypatch): + """Fixture to mock the source root dir in `ArgParseDirective`. + + Auto-used, i.e. applied to all tests by default. + Without this, the source .py files will be searched inside the pytest temp + directory, where our .py files won't be copied into. + """ + tests_dir = Path(__file__).parent.absolute() + monkeypatch.setattr(ArgParseDirective, '_srcdir', str(tests_dir)) + + @pytest.fixture(scope='session') def rootdir(): return Path(__file__).parent.absolute() / 'roots' diff --git a/test/roots/test-argparse-directive/index.rst b/test/roots/test-argparse-directive/index.rst index ee2cfa6..88f1310 100644 --- a/test/roots/test-argparse-directive/index.rst +++ b/test/roots/test-argparse-directive/index.rst @@ -2,7 +2,7 @@ Fails to parse ============== .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :index-groups: Needs; Commas diff --git a/test/roots/test-command-by-group-index/sample.rst b/test/roots/test-command-by-group-index/sample.rst index a84877b..15b442e 100644 --- a/test/roots/test-command-by-group-index/sample.rst +++ b/test/roots/test-command-by-group-index/sample.rst @@ -2,7 +2,7 @@ Sample ====== .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :nosubcommands: diff --git a/test/roots/test-command-by-group-index/subcommand-a.rst b/test/roots/test-command-by-group-index/subcommand-a.rst index d5959a8..135eed1 100644 --- a/test/roots/test-command-by-group-index/subcommand-a.rst +++ b/test/roots/test-command-by-group-index/subcommand-a.rst @@ -2,7 +2,7 @@ Command A ========= .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :path: A diff --git a/test/roots/test-command-by-group-index/subcommand-b.rst b/test/roots/test-command-by-group-index/subcommand-b.rst index 1817a39..af01701 100644 --- a/test/roots/test-command-by-group-index/subcommand-b.rst +++ b/test/roots/test-command-by-group-index/subcommand-b.rst @@ -2,7 +2,7 @@ Command B ========= .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :path: B diff --git a/test/roots/test-command-index/sample.rst b/test/roots/test-command-index/sample.rst index d7eb90b..190c293 100644 --- a/test/roots/test-command-index/sample.rst +++ b/test/roots/test-command-index/sample.rst @@ -2,6 +2,6 @@ Sample ====== .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser diff --git a/test/roots/test-command-index/subcommand-a.rst b/test/roots/test-command-index/subcommand-a.rst index 4cbbf44..8cc768d 100644 --- a/test/roots/test-command-index/subcommand-a.rst +++ b/test/roots/test-command-index/subcommand-a.rst @@ -2,7 +2,7 @@ Command A ========= .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :path: A diff --git a/test/roots/test-command-index/subcommand-b.rst b/test/roots/test-command-index/subcommand-b.rst index 53625f2..9d7abe4 100644 --- a/test/roots/test-command-index/subcommand-b.rst +++ b/test/roots/test-command-index/subcommand-b.rst @@ -2,7 +2,7 @@ Command B ========= .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :path: B diff --git a/test/roots/test-conf-opts-html/index.rst b/test/roots/test-conf-opts-html/index.rst index d7eb90b..190c293 100644 --- a/test/roots/test-conf-opts-html/index.rst +++ b/test/roots/test-conf-opts-html/index.rst @@ -2,6 +2,6 @@ Sample ====== .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser diff --git a/test/roots/test-conf-opts-html/subcommand-a.rst b/test/roots/test-conf-opts-html/subcommand-a.rst index 4cbbf44..8cc768d 100644 --- a/test/roots/test-conf-opts-html/subcommand-a.rst +++ b/test/roots/test-conf-opts-html/subcommand-a.rst @@ -2,7 +2,7 @@ Command A ========= .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :path: A diff --git a/test/roots/test-default-html/default-suppressed.rst b/test/roots/test-default-html/default-suppressed.rst index a94e8b1..f39bc42 100644 --- a/test/roots/test-default-html/default-suppressed.rst +++ b/test/roots/test-default-html/default-suppressed.rst @@ -2,6 +2,6 @@ Default suppressed ================== .. argparse:: - :filename: test/sample-default-supressed.py + :filename: sample-default-supressed.py :prog: sample-default-suppressed :func: get_parser diff --git a/test/roots/test-default-html/index.rst b/test/roots/test-default-html/index.rst index 636e7f0..5e25646 100644 --- a/test/roots/test-default-html/index.rst +++ b/test/roots/test-default-html/index.rst @@ -2,7 +2,7 @@ Sample ###### .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser diff --git a/test/roots/test-default-html/special-characters.rst b/test/roots/test-default-html/special-characters.rst index ce87bf2..497156e 100644 --- a/test/roots/test-default-html/special-characters.rst +++ b/test/roots/test-default-html/special-characters.rst @@ -2,5 +2,5 @@ Special Characters ================== .. argparse:: - :filename: test/sample-directive-special.py + :filename: sample-directive-special.py :func: get_parser diff --git a/test/roots/test-default-html/subcommand-a.rst b/test/roots/test-default-html/subcommand-a.rst index 4cbbf44..8cc768d 100644 --- a/test/roots/test-default-html/subcommand-a.rst +++ b/test/roots/test-default-html/subcommand-a.rst @@ -2,7 +2,7 @@ Command A ========= .. argparse:: - :filename: test/sample-directive-opts.py + :filename: sample-directive-opts.py :prog: sample-directive-opts :func: get_parser :path: A From fdfd09114303fa62a7102a06d122b96136102952 Mon Sep 17 00:00:00 2001 From: Robert Roos Date: Mon, 26 Jan 2026 10:06:00 +0100 Subject: [PATCH 3/4] Updated docs --- docs/usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 7b38314..397cc8a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -55,8 +55,8 @@ To document a file that is not part of a module, use :filename: :func: my_func_that_returns_a_parser :prog: script.py -The 'filename' option could be absolute path or a relative path under current -working dir. +The 'filename' option can be an absolute path or a relative one. +When a relative path is provided, the Sphinx ``srcdir`` will be taken as the base, which is the directory housing the active ``conf.py`` file. \:module\: Module name, where the function is located From bbe703c92a4be541601bbf58cf121b95154769b6 Mon Sep 17 00:00:00 2001 From: Robert Roos Date: Mon, 26 Jan 2026 10:12:38 +0100 Subject: [PATCH 4/4] Removed pass from OSerror --- sphinxarg/ext.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sphinxarg/ext.py b/sphinxarg/ext.py index de05524..2e79880 100644 --- a/sphinxarg/ext.py +++ b/sphinxarg/ext.py @@ -513,13 +513,11 @@ def _open_filename(self): try: return open(file) except OSError: - pass - - msg = ( - f'Failed to find provided source file `{self.options["filename"]}` ' - f'(resolved to `{file}`)' - ) - raise FileNotFoundError(msg) + msg = ( + f'Failed to find provided source file `{self.options["filename"]}` ' + f'(resolved to `{file}`)' + ) + raise FileNotFoundError(msg) from None def _print_subcommands(self, data, nested_content, markdown_help=False, settings=None): """