Skip to content

Commit 4f99f82

Browse files
committed
🐛 fix(titles): handle multi-word prog names in group titles
Programs with spaces in their prog (e.g. `python -m build`) had their group titles corrupted because the title-building code split on spaces to separate the prog name from subcommands. Now uses the actual root prog length to find the subcommand portion. Fixes #68
1 parent 0790dd2 commit 4f99f82

5 files changed

Lines changed: 40 additions & 10 deletions

File tree

roots/test-multiword-prog/conf.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from pathlib import Path
5+
6+
sys.path.insert(0, str(Path(__file__).parent))
7+
extensions = ["sphinx_argparse_cli"]
8+
nitpicky = True
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.. sphinx_argparse_cli::
2+
:module: parser
3+
:func: make
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from __future__ import annotations
2+
3+
from argparse import ArgumentParser
4+
5+
6+
def make() -> ArgumentParser:
7+
parser = ArgumentParser(prog="python -m build")
8+
parser.add_argument("srcdir", help="source directory")
9+
return parser

src/sphinx_argparse_cli/_logic.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ def run(self) -> list[Node]:
202202
for group in self.parser._action_groups: # noqa: SLF001
203203
if not group._group_actions or group is self.parser._subparsers: # noqa: SLF001
204204
continue
205-
home_section += self._mk_option_group(group, prefix=self.parser.prog.split("/")[-1])
205+
home_section += self._mk_option_group(
206+
group, prefix=self.parser.prog.split("/")[-1], prog=self.parser.prog.split("/")[-1]
207+
)
206208
# construct sub-parser
207209
for aliases, help_msg, parser in self.load_sub_parsers():
208210
home_section += self._mk_sub_command(aliases, help_msg, parser)
@@ -224,10 +226,10 @@ def _pre_format(self, block: None | str) -> None | paragraph | literal_block:
224226
return lit
225227
return paragraph("", Text(block))
226228

227-
def _mk_option_group(self, group: _ArgumentGroup, prefix: str) -> section:
229+
def _mk_option_group(self, group: _ArgumentGroup, prefix: str, prog: str) -> section:
228230
sub_title_prefix: str = self.options["group_sub_title_prefix"]
229231
title_prefix = self.options["group_title_prefix"]
230-
title_text = self._build_opt_grp_title(group, prefix, sub_title_prefix, title_prefix)
232+
title_text = self._build_opt_grp_title(group, prefix, prog, sub_title_prefix, title_prefix)
231233
title_ref: str = f"{prefix}{' ' if prefix else ''}{group.title}"
232234
ref_id = self.make_id(title_ref)
233235
# the text sadly needs to be prefixed, because otherwise the autosectionlabel will conflict
@@ -245,10 +247,11 @@ def _mk_option_group(self, group: _ArgumentGroup, prefix: str) -> section:
245247
group_section += opt_group
246248
return group_section
247249

248-
def _build_opt_grp_title(self, group: _ArgumentGroup, prefix: str, sub_title_prefix: str, title_prefix: str) -> str:
249-
elements = prefix.split(" ")
250-
sub_cmd = " ".join(elements[1:]) if " " in prefix else None
251-
title_text = self._resolve_prefix(elements[0], sub_cmd, prefix, title_prefix, sub_title_prefix)
250+
def _build_opt_grp_title(
251+
self, group: _ArgumentGroup, prefix: str, prog: str, sub_title_prefix: str, title_prefix: str
252+
) -> str:
253+
sub_cmd = prefix[len(prog) :].strip() or None if prefix != prog else None
254+
title_text = self._resolve_prefix(prog, sub_cmd, prefix, title_prefix, sub_title_prefix)
252255
title_text += group.title or ""
253256
return title_text
254257

@@ -363,12 +366,13 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
363366
if isinstance(group._group_actions[0], _SubParsersAction): # noqa: SLF001
364367
# If this is a subparser, ignore it
365368
continue
366-
group_section += self._mk_option_group(group, prefix=parser.prog)
369+
group_section += self._mk_option_group(group, prefix=parser.prog, prog=self.parser.prog.split("/")[-1])
367370
return group_section
368371

369372
def _build_sub_cmd_title(self, parser: ArgumentParser, sub_title_prefix: str, title_prefix: str) -> str:
370-
elements = parser.prog.split(" ")
371-
return self._resolve_prefix(elements[0], elements[1], parser.prog, title_prefix, sub_title_prefix).rstrip()
373+
root_prog = self.parser.prog.split("/")[-1]
374+
sub_cmd = parser.prog[len(root_prog) :].strip().split(" ", maxsplit=1)[0]
375+
return self._resolve_prefix(root_prog, sub_cmd, parser.prog, title_prefix, sub_title_prefix).rstrip()
372376

373377
def _resolve_prefix(
374378
self,

tests/test_logic.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,9 @@ def test_actions(build_outcome: str) -> None:
381381
assert "increase verbosity" in build_outcome
382382
assert "paths to include" in build_outcome
383383
assert "a required optional argument" in build_outcome
384+
385+
386+
@pytest.mark.sphinx(buildername="text", testroot="multiword-prog")
387+
def test_multiword_prog(build_outcome: str) -> None:
388+
assert "python -m build positional arguments" in build_outcome
389+
assert "python -m build options" in build_outcome

0 commit comments

Comments
 (0)