""" Convert an argparse parser to option directives. Inspired by sphinxcontrib.autoprogram but with a few differences: - Contains some simple pre-processing on the help messages to make the Sphinx version a bit prettier. """ import argparse import re from textwrap import dedent from docutils import nodes from docutils.parsers.rst import Directive from docutils.parsers.rst.directives import unchanged from docutils.statemachine import ViewList from sphinx.util.nodes import nested_parse_with_titles _block_re = re.compile(r":\n{2}\s{2}") _default_re = re.compile(r"Default is (.+)\.\n") _note_re = re.compile(r"Note: (.*)(?:\n\n|\n*$)", re.DOTALL) _option_line_re = re.compile(r"^(?!\s{2,}%\(prog\)s|\s{2,}--\w[\w-]*\w\b|Example: )(.+)$", re.MULTILINE) _option_re = re.compile(r"(?:^|(?<=\s))(--\w[\w-]*\w)\b") _prog_re = re.compile(r"%\(prog\)s") _percent_re = re.compile(r"%%") _cli_metadata_variables_section_cross_link_re = re.compile(r"the \"Metadata variables\" section") _inline_code_block_re = re.compile(r"(?`\" section", helptext, ) return indent(helptext) def generate_group_rst(self, group): for action in group._group_actions: # don't document suppressed parameters if action.help == argparse.SUPPRESS: continue metavar = action.metavar if isinstance(metavar, tuple): metavar = " ".join(metavar) options = [] # parameter(s) with metavar if action.option_strings and metavar: for arg in action.option_strings: # optional parameter value if action.nargs == "?": metavar = f"[{metavar}]" options.append(f"{arg} {metavar}") # positional parameter elif metavar: options.append(metavar) # parameter(s) without metavar else: options += action.option_strings directive = ".. option:: " options = f"\n{' ' * len(directive)}".join(options) yield f"{directive}{options}" yield "" for line in self.process_help(action.help).split("\n"): yield line yield "" def generate_parser_rst(self, parser, parent=None, depth=0): if depth >= len(self._headlines): return for group in parser.NESTED_ARGUMENT_GROUPS[parent]: is_parent = group in parser.NESTED_ARGUMENT_GROUPS # Exclude empty groups if not group._group_actions and not is_parent: continue title = group.title yield "" yield title yield self._headlines[depth] * len(title) yield from self.generate_group_rst(group) if is_parent: yield "" yield from self.generate_parser_rst(parser, group, depth + 1) def run(self): module = self.options.get("module") attr = self.options.get("attr") parser = get_parser(module, attr) self._available_options = [] for action in parser._actions: # positional parameters have an empty option_strings list self._available_options += action.option_strings or [action.dest] node = nodes.section() node.document = self.state.document result = ViewList() for line in self.generate_parser_rst(parser): result.append(line, "argparse") nested_parse_with_titles(self.state, result, node) return node.children def setup(app): app.add_directive("argparse", ArgparseDirective)