Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Within the reStructuredText files use the `sphinx_argparse_cli` directive that t
| description | (optional) when provided, overwrites the description and when empty, will not be included |
| epilog | (optional) when provided, overwrites the epilog and when empty, will not be included |
| usage_width | (optional) how large should usage examples be - defaults to 100 character |
| usage_first | (optional) show usage before description |
| group_title_prefix | (optional) groups subsections title prefixes, accepts the string `{prog}` as a replacement for the program name - defaults to `{prog}` |
| group_sub_title_prefix | (optional) subcommands groups subsections title prefixes, accepts replacement of `{prog}` and `{subcommand}` for program and subcommand name - defaults to `{prog} {subcommand}` |
| no_default_values | (optional) suppresses generation of `default` entries |
Expand Down
49 changes: 30 additions & 19 deletions src/sphinx_argparse_cli/_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class SphinxArgparseCli(SphinxDirective):
"description": unchanged,
"epilog": unchanged,
"usage_width": positive_int,
"usage_first": flag,
"group_title_prefix": unchanged,
"group_sub_title_prefix": unchanged,
"no_default_values": unchanged,
Expand All @@ -89,6 +90,7 @@ def __init__( # noqa: PLR0913
super().__init__(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)
self._parser: ArgumentParser | None = None
self._std_domain: StandardDomain = cast(StandardDomain, self.env.get_domain("std"))
self._raw_format: bool = False

@property
def parser(self) -> ArgumentParser:
Expand All @@ -114,6 +116,8 @@ def parser(self) -> ArgumentParser:

if "prog" in self.options:
self._parser.prog = self.options["prog"]

self._raw_format = self._parser.formatter_class == RawDescriptionHelpFormatter
return self._parser

def load_sub_parsers(self) -> Iterator[tuple[list[str], str, ArgumentParser]]:
Expand Down Expand Up @@ -148,18 +152,16 @@ def run(self) -> list[Node]:
else:
home_section = section("", title("", Text(title_text)), ids=[make_id(title_text)], names=[title_text])

raw_format = self.parser.formatter_class == RawDescriptionHelpFormatter
if "usage_first" in self.options:
home_section += self._mk_usage(self.parser)

if description := self._pre_format(self.options.get("description", self.parser.description)):
home_section += description

if "usage_first" not in self.options:
home_section += self._mk_usage(self.parser)

description = self.options.get("description", self.parser.description)
if description:
if raw_format and "\n" in description:
lit = literal_block("", Text(description))
lit["language"] = "none"
home_section += lit
else:
home_section += paragraph("", Text(description))
# construct groups excluding sub-parsers
home_section += self._mk_usage(self.parser)
for group in self.parser._action_groups: # noqa: SLF001
if not group._group_actions or group is self.parser._subparsers: # noqa: SLF001
continue
Expand All @@ -168,17 +170,20 @@ def run(self) -> list[Node]:
for aliases, help_msg, parser in self.load_sub_parsers():
home_section += self._mk_sub_command(aliases, help_msg, parser)

epilog = self.options.get("epilog", self.parser.epilog)
if epilog:
if raw_format and "\n" in epilog:
lit = literal_block("", Text(epilog))
lit["language"] = "none"
home_section += lit
else:
home_section += paragraph("", Text(epilog))
if epilog := self._pre_format(self.options.get("epilog", self.parser.epilog)):
home_section += epilog

return [home_section]

def _pre_format(self, block: None | str) -> None | paragraph | literal_block:
if block is None:
return None
if self._raw_format and "\n" in block:
lit = literal_block("", Text(block))
lit["language"] = "none"
return lit
return paragraph("", Text(block))

def _mk_option_group(self, group: _ArgumentGroup, prefix: str) -> section:
sub_title_prefix: str = self.options["group_sub_title_prefix"]
title_prefix = self.options["group_title_prefix"]
Expand Down Expand Up @@ -313,11 +318,17 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
group_section = section("", title("", Text(title_text)), ids=[ref_id], names=[title_ref])
self._register_ref(ref_id, title_ref, group_section)

if "usage_first" in self.options:
group_section += self._mk_usage(parser)

command_desc = (parser.description or help_msg or "").strip()
if command_desc:
desc_paragraph = paragraph("", Text(command_desc))
group_section += desc_paragraph
group_section += self._mk_usage(parser)

if "usage_first" not in self.options:
group_section += self._mk_usage(parser)

for group in parser._action_groups: # noqa: SLF001
if not group._group_actions: # do not show empty groups # noqa: SLF001
continue
Expand Down
7 changes: 7 additions & 0 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ def test_usage_width_custom(build_outcome: str) -> None:
assert "complex second [-h] [--flag] [--root]\n" in build_outcome


@pytest.mark.sphinx(buildername="text", testroot="complex")
@pytest.mark.prepare(directive_args=[":usage_first:"])
def test_set_usage_first(build_outcome: str) -> None:
assert "complex [-h]" in build_outcome.split("argparse tester")[0]
assert "complex first [-h]" in build_outcome.split("a-first-desc")[0]


@pytest.mark.sphinx(buildername="text", testroot="suppressed-action")
def test_suppressed_action(build_outcome: str) -> None:
assert "--activities-since" not in build_outcome
Expand Down