Skip to content

Commit 0f3c0e1

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 c0d59e0 commit 0f3c0e1

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

@@ -369,12 +372,13 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
369372
if isinstance(group._group_actions[0], _SubParsersAction): # noqa: SLF001
370373
# If this is a subparser, ignore it
371374
continue
372-
group_section += self._mk_option_group(group, prefix=parser.prog)
375+
group_section += self._mk_option_group(group, prefix=parser.prog, prog=self.parser.prog.split("/")[-1])
373376
return group_section
374377

375378
def _build_sub_cmd_title(self, parser: ArgumentParser, sub_title_prefix: str, title_prefix: str) -> str:
376-
elements = parser.prog.split(" ")
377-
return self._resolve_prefix(elements[0], elements[1], parser.prog, title_prefix, sub_title_prefix).rstrip()
379+
root_prog = self.parser.prog.split("/")[-1]
380+
sub_cmd = parser.prog[len(root_prog) :].strip().split(" ", maxsplit=1)[0]
381+
return self._resolve_prefix(root_prog, sub_cmd, parser.prog, title_prefix, sub_title_prefix).rstrip()
378382

379383
def _resolve_prefix(
380384
self,

tests/test_logic.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,9 @@ def test_tuple_metavar(build_outcome: str) -> None:
390390
assert "select a pair" in build_outcome
391391
assert "default: None" not in build_outcome
392392
assert '"VAL"' in build_outcome
393+
394+
395+
@pytest.mark.sphinx(buildername="text", testroot="multiword-prog")
396+
def test_multiword_prog(build_outcome: str) -> None:
397+
assert "python -m build positional arguments" in build_outcome
398+
assert "python -m build options" in build_outcome

0 commit comments

Comments
 (0)