docs: fix custom ext_argparse Sphinx extension

- Move the `parser_helper()` function from `streamlink_cli.main`
  into the `streamlink_cli._parser` module
- Refactor `ext_argparse` and set default import paths
- Update import path in `build-shell-completions.sh`

Unfortunately, the `streamlink_cli._parser` module can't be excluded
from the sdist/wheel distributions, as packagers might build
shell completions from the installed `streamlink_cli` package
instead of using the pre-built shell completions included in the sdist
or building them from an editable install.
This commit is contained in:
bastimeyer 2024-02-09 01:22:55 +01:00 committed by Sebastian Meyer
parent f2b16ede21
commit 0acbca3af1
6 changed files with 39 additions and 22 deletions

View File

@ -31,8 +31,6 @@ Options
-------
.. argparse::
:module: streamlink_cli.main
:attr: parser_helper
Bugs

View File

@ -24,5 +24,3 @@ Command-line usage
.. argparse::
:module: streamlink_cli.main
:attr: parser_helper

View File

@ -9,12 +9,14 @@ Inspired by sphinxcontrib.autoprogram but with a few differences:
import argparse
import re
from importlib import import_module
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.errors import ExtensionError
from sphinx.util.nodes import nested_parse_with_titles
@ -29,12 +31,6 @@ _inline_code_block_re = re.compile(r"(?<!`)`([^`]+?)`")
_example_inline_code_block_re = re.compile(r"(?<=^Example: )(.+)$", re.MULTILINE)
def get_parser(module_name, attr):
module = __import__(module_name, globals(), locals(), [attr])
parser = getattr(module, attr)
return parser if not callable(parser) else parser()
def indent(value, length=4):
space = " " * length
return "\n".join(space + line for line in value.splitlines())
@ -49,6 +45,23 @@ class ArgparseDirective(Directive):
_headlines = ["^", "~"]
_DEFAULT_MODULE = "streamlink_cli._parser"
_DEFAULT_ATTR = "get_parser"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._available_options = []
@staticmethod
def get_parser(module: str, attr: str) -> argparse.ArgumentParser:
try:
mod = import_module(module)
obj = getattr(mod, attr)
except Exception as err:
raise ExtensionError("Invalid ext_argparse module or attr value") from err
return obj() if callable(obj) else obj
def process_help(self, helptext):
# Dedent the help to make sure we are always dealing with
# non-indented text.
@ -156,11 +169,10 @@ class ArgparseDirective(Directive):
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)
module = self.options.get("module", self._DEFAULT_MODULE)
attr = self.options.get("attr", self._DEFAULT_ATTR)
parser = self.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]

View File

@ -23,7 +23,7 @@ for shell in "${!COMPLETIONS[@]}"; do
python -m shtab \
"--shell=${shell}" \
--error-unimportable \
streamlink_cli.main.parser_helper \
streamlink_cli._parser.get_parser \
> "${dist}"
echo "Completions for ${shell} written to ${dist}"
done

View File

@ -0,0 +1,16 @@
"""
Utility module for importing Streamlink's argparse.ArgumentParser instance
when building the documentation, man page or command-line shell completions.
"""
from streamlink.session import Streamlink
from streamlink_cli.argparser import build_parser
from streamlink_cli.main import setup_plugin_args
def get_parser():
session = Streamlink(plugins_builtin=True)
parser = build_parser()
setup_plugin_args(session, parser)
return parser

View File

@ -952,10 +952,3 @@ def main():
)
sys.exit(error_code)
def parser_helper():
session = Streamlink(plugins_builtin=True)
parser = build_parser()
setup_plugin_args(session, parser)
return parser