pax_global_header00006660000000000000000000000064141253477660014531gustar00rootroot0000000000000052 comment=ff5d3bc20c53340616552cdd9e15334309840915 sphinx-click-3.0.2/000077500000000000000000000000001412534776600141275ustar00rootroot00000000000000sphinx-click-3.0.2/.editorconfig000066400000000000000000000012001412534776600165750ustar00rootroot00000000000000# -*- coding: utf-8 -*- root = true [*] indent_style = space end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 # Python files [*.py] indent_size = 4 # isort plugin configuration known_first_party = sphinx_click multi_line_output = 2 default_section = THIRDPARTY skip = .eggs docs # RST files (used by sphinx) [*.rst] indent_size = 3 # CSS, HTML, JS, JSON, YML [*.{css,html,js,json,yml}] indent_size = 2 # Matches the exact files either package.json or .travis.yml [{package.json,.travis.yml}] indent_size = 2 # Dockerfile [Dockerfile] indent_size = 4 # Makefile [Makefile] indent_size = 4 sphinx-click-3.0.2/.gitignore000066400000000000000000000013201412534776600161130ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Sphinx documentation docs/_build/ # pbr AUTHORS ChangeLog # virtualenv /.venv # vim *.swp sphinx-click-3.0.2/.pre-commit-config.yaml000066400000000000000000000014701412534776600204120ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks --- default_language_version: # force all unspecified python hooks to run python3 python: python3 repos: - repo: https://github.com/ambv/black rev: stable hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.1.0 hooks: - id: trailing-whitespace - id: mixed-line-ending args: ['--fix', 'lf'] - id: check-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements - id: end-of-file-fixer - id: check-yaml files: .*\.(yaml|yml)$ - id: check-added-large-files - repo: https://gitlab.com/pycqa/flake8 rev: 3.8.3 hooks: - id: flake8 sphinx-click-3.0.2/.travis.yml000066400000000000000000000023021412534776600162350ustar00rootroot00000000000000--- language: python cache: pip python: - 3.6 - 3.7 - 3.8 - 3.9 install: - pip install tox-travis - pip install codecov script: - tox after_success: - codecov stages: - test - deploy jobs: include: - stage: deploy python: 3.6 install: skip # no tests, no depedencies needed script: skip # we're not running tests deploy: provider: pypi user: stephenfin password: secure: "VhbttK0qaGjz7ISIUEAVxPEn8Qo5vu0kRT97C9WykI7BkrkceuSj7c+neCFlXpsUm3VnyzmUgYzDMHBI/EbmzrsX48lTC0GKhDpEBV/gxVmWE+2tdSqYXC1yyBbMFhEm7ygCle8RQomAptWO2C7lcoYazvwT8kWngi15/KKzgUizC5lqiv2VyW8kzPg9qTSGft0IUAA8vVYjlCIlgYVcpk3tHSzOvedsrGd6XZEPQlGtDdeUiDHQ2usxpdOKWJ+Mq6nxpwqwOHvPUB2WvWoYFKfZZrESf9EpnuUrc5JFTbUN9k1RUnsW9DAcg/qNA37vFK1hyGT5RDNCQr90g+j7cUtYicOLA6nnzHE3ccH9oFVCx5gNs1IWEES7hpfMDIqHR5EQy6ez5mzjb8EDHZn+mxjHeSL45kECGiDwrrbqXsfFXIZtc+stX8RHRJupx6wSO+hjbkQvIDdWWeAw8nEhWqHq4LPNM/5S3ZUUx0/YyYME0tJ5nr6U1zNzVjQ3cumjq8bmQbZKd80/X/a9dc9hjFfD3gIyKL7mvwnb5/igBTdk+ymhyUMxAhX0Sx94eZpVd4nXCVSTLCwVMel+z2wRU/hWQurNUhtSv7lZ4Y1/88HsbQ+aB98j8EtwV59IsCSsUUQdTtiYOcwUKZqrVRm+cWD2heT5eBMyB1d+BYjLEWY=" on: tags: true distributions: sdist bdist_wheel sphinx-click-3.0.2/LICENSE000066400000000000000000000021071412534776600151340ustar00rootroot00000000000000The MIT License Copyright (c) 2017 Stephen Finucane http://that.guru/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sphinx-click-3.0.2/README.rst000066400000000000000000000026501412534776600156210ustar00rootroot00000000000000============ sphinx-click ============ .. image:: https://travis-ci.org/click-contrib/sphinx-click.svg?branch=master :target: https://travis-ci.org/click-contrib/sphinx-click :alt: CI Status .. image:: https://readthedocs.org/projects/sphinx-click/badge/?version=latest :target: https://sphinx-click.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status `sphinx-click` is a `Sphinx`__ plugin that allows you to automatically extract documentation from a `click-based`__ application and include it in your docs. __ http://www.sphinx-doc.org/ __ http://click.pocoo.org/ Installation ------------ Install the plugin using `pip`: .. code-block:: shell $ pip install sphinx-click Alternatively, install from source by cloning this repo then running `setup.py`: .. code-block:: shell $ python setup.py install Usage ----- .. important:: To document a click-based application, both the application itself and any additional dependencies required by that application **must be installed**. Enable the plugin in your Sphinx `conf.py` file: .. code-block:: python extensions = ['sphinx_click'] Once enabled, you can now use the plugin wherever necessary in the documentation. .. code-block:: .. click:: module:parser :prog: hello-world :nested: full Detailed information on the various options available is provided in the `documentation `_. sphinx-click-3.0.2/docs/000077500000000000000000000000001412534776600150575ustar00rootroot00000000000000sphinx-click-3.0.2/docs/changelog.rst000066400000000000000000000000751412534776600175420ustar00rootroot00000000000000Changes ======= .. include:: ../ChangeLog :start-line: 2 sphinx-click-3.0.2/docs/conf.py000066400000000000000000000053471412534776600163670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # sphinx-click documentation build configuration file # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.5' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx_click"] # Add any paths that contain templates here, relative to this directory. templates_path = [] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'sphinx-click' copyright = u'2017, Stephen Finucane' author = u'Stephen Finucane' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'' # The full version, including alpha/beta/rc tags. release = u'' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [] sphinx-click-3.0.2/docs/contributing.rst000066400000000000000000000022601412534776600203200ustar00rootroot00000000000000Contribution ============ We welcome all contributions to `sphinx-click`. Support ------- Open and issue in the `issue tracker`_ for all support requests. `StackOverflow`_ is also worth considering. Reporting Issues ---------------- Report all issues in the `issue tracker`_. When doing so, please include version information for: - Python - `click` - `sphinx-click` Submitting Patches ------------------ All patches should be submitted as pull requests on the `GitHub project`_. - Include tests if fixing a bug - Clearly explain what you're trying to accomplish - Follow :pep:`8`. You can use the `pep8` tox target for this Testing ------- `sphinx-click` uses `tox` and `unittest` for testing. To run all tests, run: .. code-block:: shell $ tox We support a number of Python versions. To list available environments, run: .. code-block:: shell $ tox --list To run one of these environments, such as `py27` which runs tests under Python 2.7, run: .. code-block:: shell $ tox -e py27 .. _issue tracker: https://github.com/click-contrib/sphinx-click/issues .. _StackOverflow: https://stackoverflow.com .. _GitHub project: https://github.com/click-contrib/sphinx-click sphinx-click-3.0.2/docs/examples/000077500000000000000000000000001412534776600166755ustar00rootroot00000000000000sphinx-click-3.0.2/docs/examples/commandcollections.rst000066400000000000000000000011001412534776600232740ustar00rootroot00000000000000Documenting |CommandCollection| ================================= Consider the following sample application, using |CommandCollection|_: .. literalinclude:: ../../examples/commandcollections/cli.py This can be documented using *sphinx-click* like so: .. code-block:: rst .. click:: commandcollections.cli:cli :prog: cli :nested: full ---- .. click:: commandcollections.cli:cli :prog: cli :nested: full .. |CommandCollection| replace:: ``CommandCollection`` .. _CommandCollection: https://click.palletsprojects.com/en/7.x/api/#click.CommandCollection sphinx-click-3.0.2/docs/examples/index.rst000066400000000000000000000001001412534776600205250ustar00rootroot00000000000000Examples ======== .. toctree:: :maxdepth: 1 :glob: * sphinx-click-3.0.2/docs/index.rst000066400000000000000000000011421412534776600167160ustar00rootroot00000000000000sphinx-click ============ :mod:`sphinx-click ` is a `Sphinx`__ plugin that allows you to automatically extract documentation from a `click-based`__ application and include it in your docs. __ http://www.sphinx-doc.org/ __ http://click.pocoo.org/ .. toctree:: :maxdepth: 2 installation usage contributing changelog examples/index .. seealso:: Module :mod:`click` This extension assumes you are using :mod:`click` to create your command line application. Module :mod:`sphinxcontrib.autoprogram` An equivalent library for use with :mod:`argparse`. sphinx-click-3.0.2/docs/installation.rst000066400000000000000000000005371412534776600203170ustar00rootroot00000000000000Installation ============ Install the plugin using `pip`: .. code-block:: shell $ pip install sphinx-click Alternatively, install from source by cloning this repo then running `setup.py`: .. code-block:: shell $ python setup.py install .. important:: Both the package you're referencing and any dependencies **must be installed**. sphinx-click-3.0.2/docs/usage.rst000066400000000000000000000136231412534776600167220ustar00rootroot00000000000000Usage ===== To enable the plugin, add the extension to the list of extensions in your Sphinx `conf.py` file: .. code-block:: python extensions = ['sphinx_click'] Once enabled, *sphinx-click* enables automatic documentation for `click-based`_ applications by way of a `Sphinx directive`_. .. rst:directive:: .. click:: module:parser Automatically extract documentation from a `click-based`_ application and include it in your docs. .. code-block:: rst .. click:: module:parser :prog: hello-world :nested: full The directive takes the import name of a *click* object as its sole argument. This should be a subclass of |click.core.BaseCommand|_, such as ``click.Command``, ``click.Group``, ``click.MultiCommand``, etc. In addition, the following options are required: ``:prog:`` The name of your tool (or how it should appear in your documentation). For example, if you run your script as ``./boo --opts args`` then ``:prog:`` will be ``boo``. If this is not given, the module name is used. The following options are optional: ``:nested:`` Whether subcommands should also be shown. One of: ``full`` List sub-commands with full documentation. ``short`` List sub-commands with short documentation. ``none`` Do not list sub-commands. Defaults to ``short`` unless ``show-nested`` (deprecated) is set. ``:commands:`` Document only listed commands. ``:show-nested:`` This option is deprecated; use ``nested`` instead. The generated documentation includes anchors for the generated commands, their options and their environment variables using the `Sphinx standard domain`_. .. _Sphinx directive: http://www.sphinx-doc.org/en/stable/extdev/markupapi.html .. _click-based: http://click.pocoo.org/6/ .. _Sphinx standard domain: http://www.sphinx-doc.org/en/stable/domains.html#the-standard-domain .. |click.core.BaseCommand| replace:: ``click.core.BaseCommand`` .. _click.core.BaseCommand: http://click.pocoo.org/6/api/#click.BaseCommand Example ------- Take the below ``click`` application, which is defined in the ``hello_world`` module: .. code-block:: python import click @click.group() def greet(): """A sample command group.""" pass @greet.command() @click.argument('user', envvar='USER') def hello(user): """Greet a user.""" click.echo('Hello %s' % user) @greet.command() def world(): """Greet the world.""" click.echo('Hello world!') To document this, use the following: .. code-block:: rst .. click:: hello_world:greet :prog: hello-world By default, the subcommand, ``hello``, is listed but no documentation provided. If you wish to include full documentation for the subcommand in the output, configure the ``nested`` flag to ``full``. .. code-block:: rst .. click:: hello_world:greet :prog: hello-world :nested: full .. note:: The ``nested`` flag replaces the deprecated ``show-nested`` flag. Conversely, if you do not wish to list these subcommands or wish to handle them separately, configure the ``nested`` flag to ``none``. .. code-block:: rst .. click:: hello_world:greet :prog: hello-world :nested: none You can also document only selected commands by using ``:commands:`` option. .. code-block:: rst .. click:: hello_world:greet :prog: hello-world :commands: hello You can cross-reference the commands, option and environment variables using the roles provided by the `Sphinx standard domain`_. .. code-block:: rst .. click:: hello_world:greet :prog: hello-world The :program:`hello` command accepts a :option:`user` argument. If this is not provided, the :envvar:`USER` environment variable will be used. .. note:: Cross-referencing using the ``:program:`` directive is not currently supported by Sphinx. Refer to the `Sphinx issue`__ for more information. __ https://github.com/sphinx-doc/sphinx/issues/880 Documenting |CommandCollection|_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When building more complex CLI, one might need to bring together multiple groups of commands and make them accessible using a single client with |CommandCollection|_. *sphinx-click* renders collection of commands with multiple sections, one for each group listed in the command ``sources``. The group names are used as section titles and the help string from the description are used as section description. Thus, a client defined using a |CommandCollection| as ``cli`` can be rendered using *sphinx-click* and the following directive: .. code-block:: rst .. click:: cli:cli :prog: cli :nested: full This will render the subcommands of each group in different sections, one for each group in ``sources``. An example is provided in :doc:`examples/commandcollections`. Modifying ``sys.path`` ---------------------- If the application or script you wish to document is not installed (i.e. you have not installed it with *pip* or run ``python setup.py``), then you may need to modify ``sys.path``. For example, given the following application:: git |- git | |- __init__.py | \- git.py \- docs |- git.rst |- index.rst \- conf.py then it would be necessary to add the following to ``git/docs/conf.py``: .. code-block:: python import os import sys sys.path.insert(0, os.path.abspath('..')) Once done, you could include the following in ``git/docs/git.rst`` to document the application: .. code-block:: rst .. click:: git.git:cli :prog: git :nested: full assuming the group or command in ``git.git`` is named ``cli``. Refer to `issue #2 `__ for more information. .. |CommandCollection| replace:: :code:`CommandCollection` .. _CommandCollection: https://click.palletsprojects.com/en/7.x/api/#click.CommandCollection sphinx-click-3.0.2/examples/000077500000000000000000000000001412534776600157455ustar00rootroot00000000000000sphinx-click-3.0.2/examples/commandcollections/000077500000000000000000000000001412534776600216225ustar00rootroot00000000000000sphinx-click-3.0.2/examples/commandcollections/__init__.py000066400000000000000000000000001412534776600237210ustar00rootroot00000000000000sphinx-click-3.0.2/examples/commandcollections/cli.py000066400000000000000000000013631412534776600227460ustar00rootroot00000000000000# file: cli.py import click main = click.Group( name='Principal Commands', help="Principal commands that are used in ``cli``.\n\n" "The section name and description are obtained using the name and " "description of the group passed as sources for |CommandCollection|_.", ) @main.command(help='CMD 1') def cmd1(): print('call cmd 1') helpers = click.Group(name='Helper Commands', help="Helper commands for ``cli``.") @helpers.command() def cmd2(): "Helper command that has no option." pass @helpers.command() @click.option('--user', type=str) def cmd3(user): "Helper command with an option." pass cli = click.CommandCollection( name='cli', sources=[main, helpers], help='Some general info on ``cli``.' ) sphinx-click-3.0.2/examples/setup.py000066400000000000000000000002311412534776600174530ustar00rootroot00000000000000from setuptools import find_packages, setup setup( name='sphinx_click_examples', packages=find_packages('.'), install_requires=['click'], ) sphinx-click-3.0.2/pyproject.toml000066400000000000000000000003011412534776600170350ustar00rootroot00000000000000[tool.black] line-length = 88 target-version = ['py36'] skip-string-normalization = true exclude = ''' ( /( \.eggs | \.git | \.tox | \.venv | build | dist ) ) ''' sphinx-click-3.0.2/requirements.txt000066400000000000000000000000401412534776600174050ustar00rootroot00000000000000sphinx>=2.0 click>=7.0 docutils sphinx-click-3.0.2/setup.cfg000066400000000000000000000014061412534776600157510ustar00rootroot00000000000000[metadata] name = sphinx-click summary = Sphinx extension that automatically documents click applications description-file = README.rst license = MIT License classifiers = Programming Language :: Python :: 3 Programming Language :: Python Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: MIT License Operating System :: OS Independent python_requires = >=3.6 keywords = sphinx author = Stephen Finucane author-email = stephen@that.guru home-page = https://github.com/stephenfin/sphinx-click project-url = https://that.guru/ [files] packages = sphinx_click [flake8] max-line-length = 88 ignore = E203,E501,E741,W503 sphinx-click-3.0.2/setup.py000066400000000000000000000001551412534776600156420ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup setup( setup_requires=['pbr>=2.0'], pbr=True, ) sphinx-click-3.0.2/sphinx_click/000077500000000000000000000000001412534776600166055ustar00rootroot00000000000000sphinx-click-3.0.2/sphinx_click/__init__.py000066400000000000000000000000531412534776600207140ustar00rootroot00000000000000from sphinx_click.ext import setup # noqa sphinx-click-3.0.2/sphinx_click/ext.py000066400000000000000000000351571412534776600177720ustar00rootroot00000000000000import re import traceback import warnings import click from docutils import nodes from docutils.parsers import rst from docutils.parsers.rst import directives from docutils import statemachine from sphinx.util import logging from sphinx.util import nodes as sphinx_nodes LOG = logging.getLogger(__name__) NESTED_FULL = 'full' NESTED_SHORT = 'short' NESTED_NONE = 'none' ANSI_ESC_SEQ_RE = re.compile(r'\x1B\[\d+(;\d+){0,2}m', flags=re.MULTILINE) def _indent(text, level=1): prefix = ' ' * (4 * level) def prefixed_lines(): for line in text.splitlines(True): yield (prefix + line if line.strip() else line) return ''.join(prefixed_lines()) def _get_usage(ctx): """Alternative, non-prefixed version of 'get_usage'.""" formatter = ctx.make_formatter() pieces = ctx.command.collect_usage_pieces(ctx) formatter.write_usage(ctx.command_path, ' '.join(pieces), prefix='') return formatter.getvalue().rstrip('\n') def _get_help_record(opt): """Re-implementation of click.Opt.get_help_record. The variant of 'get_help_record' found in Click makes uses of slashes to separate multiple opts, and formats option arguments using upper case. This is not compatible with Sphinx's 'option' directive, which expects comma-separated opts and option arguments surrounded by angle brackets [1]. [1] http://www.sphinx-doc.org/en/stable/domains.html#directive-option """ def _write_opts(opts): rv, _ = click.formatting.join_options(opts) if not opt.is_flag and not opt.count: name = opt.name if opt.metavar: name = opt.metavar.lstrip('<[{($').rstrip('>]})$') rv += ' <{}>'.format(name) return rv rv = [_write_opts(opt.opts)] if opt.secondary_opts: rv.append(_write_opts(opt.secondary_opts)) out = [] if opt.help: if opt.required: out.append('**Required** %s' % opt.help) else: out.append(opt.help) else: if opt.required: out.append('**Required**') extras = [] if opt.default is not None and opt.show_default: if isinstance(opt.show_default, str): # Starting from Click 7.0 this can be a string as well. This is # mostly useful when the default is not a constant and # documentation thus needs a manually written string. extras.append(':default: %s' % opt.show_default) else: extras.append( ':default: %s' % ( ', '.join('%s' % d for d in opt.default) if isinstance(opt.default, (list, tuple)) else opt.default, ) ) if isinstance(opt.type, click.Choice): extras.append(':options: %s' % ' | '.join(str(x) for x in opt.type.choices)) if extras: if out: out.append('') out.extend(extras) return ', '.join(rv), '\n'.join(out) def _format_description(ctx): """Format the description for a given `click.Command`. We parse this as reStructuredText, allowing users to embed rich information in their help messages if they so choose. """ help_string = ctx.command.help or ctx.command.short_help if not help_string: return help_string = ANSI_ESC_SEQ_RE.sub('', help_string) bar_enabled = False for line in statemachine.string2lines( help_string, tab_width=4, convert_whitespace=True ): if line == '\b': bar_enabled = True continue if line == '': bar_enabled = False line = '| ' + line if bar_enabled else line yield line yield '' def _format_usage(ctx): """Format the usage for a `click.Command`.""" yield '.. code-block:: shell' yield '' for line in _get_usage(ctx).splitlines(): yield _indent(line) yield '' def _format_option(opt): """Format the output for a `click.Option`.""" opt = _get_help_record(opt) yield '.. option:: {}'.format(opt[0]) if opt[1]: yield '' for line in statemachine.string2lines( ANSI_ESC_SEQ_RE.sub('', opt[1]), tab_width=4, convert_whitespace=True ): yield _indent(line) def _format_options(ctx): """Format all `click.Option` for a `click.Command`.""" # the hidden attribute is part of click 7.x only hence use of getattr params = [ param for param in ctx.command.params if isinstance(param, click.Option) and not getattr(param, 'hidden', False) ] for param in params: for line in _format_option(param): yield line yield '' def _format_argument(arg): """Format the output of a `click.Argument`.""" yield '.. option:: {}'.format(arg.human_readable_name) yield '' yield _indent( '{} argument{}'.format( 'Required' if arg.required else 'Optional', '(s)' if arg.nargs != 1 else '' ) ) def _format_arguments(ctx): """Format all `click.Argument` for a `click.Command`.""" params = [x for x in ctx.command.params if isinstance(x, click.Argument)] for param in params: for line in _format_argument(param): yield line yield '' def _format_envvar(param): """Format the envvars of a `click.Option` or `click.Argument`.""" yield '.. envvar:: {}'.format(param.envvar) yield ' :noindex:' yield '' if isinstance(param, click.Argument): param_ref = param.human_readable_name else: # if a user has defined an opt with multiple "aliases", always use the # first. For example, if '--foo' or '-f' are possible, use '--foo'. param_ref = param.opts[0] yield _indent('Provide a default for :option:`{}`'.format(param_ref)) def _format_envvars(ctx): """Format all envvars for a `click.Command`.""" params = [x for x in ctx.command.params if getattr(x, 'envvar')] for param in params: yield '.. _{command_name}-{param_name}-{envvar}:'.format( command_name=ctx.command_path.replace(' ', '-'), param_name=param.name, envvar=param.envvar, ) yield '' for line in _format_envvar(param): yield line yield '' def _format_subcommand(command): """Format a sub-command of a `click.Command` or `click.Group`.""" yield '.. object:: {}'.format(command.name) short_help = command.get_short_help_str() if short_help: yield '' for line in statemachine.string2lines( short_help, tab_width=4, convert_whitespace=True ): yield _indent(line) def _format_epilog(ctx): """Format the epilog for a given `click.Command`. We parse this as reStructuredText, allowing users to embed rich information in their help messages if they so choose. """ if not ctx.command.epilog: return for line in statemachine.string2lines( ANSI_ESC_SEQ_RE.sub('', ctx.command.epilog), tab_width=4, convert_whitespace=True, ): yield line yield '' def _get_lazyload_commands(multicommand): commands = {} for command in multicommand.list_commands(multicommand): commands[command] = multicommand.get_command(multicommand, command) return commands def _filter_commands(ctx, commands=None): """Return list of used commands.""" lookup = getattr(ctx.command, 'commands', {}) if not lookup and isinstance(ctx.command, click.MultiCommand): lookup = _get_lazyload_commands(ctx.command) if commands is None: return sorted(lookup.values(), key=lambda item: item.name) names = [name.strip() for name in commands.split(',')] return [lookup[name] for name in names if name in lookup] def _format_command(ctx, nested, commands=None): """Format the output of `click.Command`.""" if ctx.command.hidden: return # description for line in _format_description(ctx): yield line yield '.. program:: {}'.format(ctx.command_path) # usage for line in _format_usage(ctx): yield line # options lines = list(_format_options(ctx)) if lines: # we use rubric to provide some separation without exploding the table # of contents yield '.. rubric:: Options' yield '' for line in lines: yield line # arguments lines = list(_format_arguments(ctx)) if lines: yield '.. rubric:: Arguments' yield '' for line in lines: yield line # environment variables lines = list(_format_envvars(ctx)) if lines: yield '.. rubric:: Environment variables' yield '' for line in lines: yield line # description for line in _format_epilog(ctx): yield line # if we're nesting commands, we need to do this slightly differently if nested in (NESTED_FULL, NESTED_NONE): return commands = _filter_commands(ctx, commands) if commands: yield '.. rubric:: Commands' yield '' for command in commands: # Don't show hidden subcommands if command.hidden: continue for line in _format_subcommand(command): yield line yield '' def nested(argument): values = (NESTED_FULL, NESTED_SHORT, NESTED_NONE) if not argument: return None if argument not in values: raise ValueError( "%s is not a valid value for ':nested:'; allowed values: %s" % directives.format_values(values) ) return argument class ClickDirective(rst.Directive): has_content = False required_arguments = 1 option_spec = { 'prog': directives.unchanged_required, 'nested': nested, 'commands': directives.unchanged, 'show-nested': directives.flag, } def _load_module(self, module_path): """Load the module.""" # __import__ will fail on unicode, # so we ensure module path is a string here. module_path = str(module_path) try: module_name, attr_name = module_path.split(':', 1) except ValueError: # noqa raise self.error( '"{}" is not of format "module:parser"'.format(module_path) ) try: mod = __import__(module_name, globals(), locals(), [attr_name]) except (Exception, SystemExit) as exc: # noqa err_msg = 'Failed to import "{}" from "{}". '.format(attr_name, module_name) if isinstance(exc, SystemExit): err_msg += 'The module appeared to call sys.exit()' else: err_msg += 'The following exception was raised:\n{}'.format( traceback.format_exc() ) raise self.error(err_msg) if not hasattr(mod, attr_name): raise self.error( 'Module "{}" has no attribute "{}"'.format(module_name, attr_name) ) parser = getattr(mod, attr_name) if not isinstance(parser, click.BaseCommand): raise self.error( '"{}" of type "{}" is not derived from ' '"click.BaseCommand"'.format(type(parser), module_path) ) return parser def _generate_nodes( self, name, command, parent, nested, commands=None, semantic_group=False ): """Generate the relevant Sphinx nodes. Format a `click.Group` or `click.Command`. :param name: Name of command, as used on the command line :param command: Instance of `click.Group` or `click.Command` :param parent: Instance of `click.Context`, or None :param nested: The granularity of subcommand details. :param commands: Display only listed commands or skip the section if empty :param semantic_group: Display command as title and description for CommandCollection. :returns: A list of nested docutil nodes """ ctx = click.Context(command, info_name=name, parent=parent) if command.hidden: return [] # Title section = nodes.section( '', nodes.title(text=name), ids=[nodes.make_id(ctx.command_path)], names=[nodes.fully_normalize_name(ctx.command_path)], ) # Summary source_name = ctx.command_path result = statemachine.ViewList() if semantic_group: lines = _format_description(ctx) else: lines = _format_command(ctx, nested, commands) for line in lines: LOG.debug(line) result.append(line, source_name) sphinx_nodes.nested_parse_with_titles(self.state, result, section) # Subcommands if nested == NESTED_FULL: if isinstance(command, click.CommandCollection): for source in command.sources: section.extend( self._generate_nodes( source.name, source, parent=ctx, nested=nested, semantic_group=True, ) ) else: commands = _filter_commands(ctx, commands) for command in commands: parent = ctx if not semantic_group else ctx.parent section.extend( self._generate_nodes( command.name, command, parent=parent, nested=nested ) ) return [section] def run(self): self.env = self.state.document.settings.env command = self._load_module(self.arguments[0]) if 'prog' not in self.options: raise self.error(':prog: must be specified') prog_name = self.options.get('prog') show_nested = 'show-nested' in self.options nested = self.options.get('nested') if show_nested: if nested: raise self.error( "':nested:' and ':show-nested:' are mutually exclusive" ) else: warnings.warn( "':show-nested:' is deprecated; use ':nested: full'", DeprecationWarning, ) nested = NESTED_FULL if show_nested else NESTED_SHORT commands = self.options.get('commands') return self._generate_nodes(prog_name, command, None, nested, commands) def setup(app): app.add_directive('click', ClickDirective) return { 'parallel_read_safe': True, 'parallel_write_safe': True, } sphinx-click-3.0.2/test-requirements.txt000066400000000000000000000000421412534776600203640ustar00rootroot00000000000000click>=5.0,<8.0 coverage>4.3,<5.0 sphinx-click-3.0.2/tests/000077500000000000000000000000001412534776600152715ustar00rootroot00000000000000sphinx-click-3.0.2/tests/__init__.py000066400000000000000000000000001412534776600173700ustar00rootroot00000000000000sphinx-click-3.0.2/tests/test_formatter.py000066400000000000000000000455701412534776600207200ustar00rootroot00000000000000import textwrap import unittest import click from sphinx_click import ext class CommandTestCase(unittest.TestCase): """Validate basic ``click.Command`` instances.""" maxDiff = None def test_no_parameters(self): """Validate a `click.Command` with no parameters. This exercises the code paths for a command with *no* arguments, *no* options and *no* environment variables. """ @click.command() def foobar(): """A sample command.""" pass ctx = click.Context(foobar, info_name='foobar') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command. .. program:: foobar .. code-block:: shell foobar [OPTIONS] """ ).lstrip(), '\n'.join(output), ) def test_basic_parameters(self): """Validate a combination of parameters. This exercises the code paths for a command with arguments, options and environment variables. """ @click.command() @click.option('--param', envvar='PARAM', help='A sample option') @click.option('--another', metavar='[FOO]', help='Another option') @click.option( '--choice', help='A sample option with choices', type=click.Choice(['Option1', 'Option2']), ) @click.option( '--numeric-choice', metavar='', help='A sample option with numeric choices', type=click.Choice([1, 2, 3]), ) @click.argument('ARG', envvar='ARG') def foobar(bar): """A sample command.""" pass ctx = click.Context(foobar, info_name='foobar') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command. .. program:: foobar .. code-block:: shell foobar [OPTIONS] ARG .. rubric:: Options .. option:: --param A sample option .. option:: --another Another option .. option:: --choice A sample option with choices :options: Option1 | Option2 .. option:: --numeric-choice A sample option with numeric choices :options: 1 | 2 | 3 .. rubric:: Arguments .. option:: ARG Required argument .. rubric:: Environment variables .. _foobar-param-PARAM: .. envvar:: PARAM :noindex: Provide a default for :option:`--param` .. _foobar-arg-ARG: .. envvar:: ARG :noindex: Provide a default for :option:`ARG` """ ).lstrip(), '\n'.join(output), ) def test_help_epilog(self): """Validate formatting of explicit help and epilog strings.""" @click.command(help='A sample command.', epilog='A sample epilog.') @click.option('--param', help='A sample option') def foobar(bar): pass ctx = click.Context(foobar, info_name='foobar') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command. .. program:: foobar .. code-block:: shell foobar [OPTIONS] .. rubric:: Options .. option:: --param A sample option A sample epilog. """ ).lstrip(), '\n'.join(output), ) def test_defaults(self): """Validate formatting of user documented defaults.""" @click.command() @click.option('--num-param', type=int, default=42, show_default=True) @click.option( '--param', default=lambda: None, show_default='Something computed at runtime', ) def foobar(bar): """A sample command.""" pass ctx = click.Context(foobar, info_name='foobar') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command. .. program:: foobar .. code-block:: shell foobar [OPTIONS] .. rubric:: Options .. option:: --num-param :default: 42 .. option:: --param :default: Something computed at runtime """ ).lstrip(), '\n'.join(output), ) def test_hidden(self): """Validate a `click.Command` with the `hidden` flag.""" @click.command(hidden=True) def foobar(): """A sample command.""" pass ctx = click.Context(foobar, info_name='foobar') output = list(ext._format_command(ctx, nested='short')) self.assertEqual('', '\n'.join(output)) def test_titles(self): """Validate a `click.Command` with nested titles.""" @click.command() @click.option('--name', help='Name to say hello to.', required=True, type=str) def hello(name): """Prints hello to name given. Examples -------- .. code:: bash my_cli hello --name "Jack" """ ctx = click.Context(hello, info_name='hello') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ Prints hello to name given. Examples -------- .. code:: bash my_cli hello --name "Jack" .. program:: hello .. code-block:: shell hello [OPTIONS] .. rubric:: Options .. option:: --name **Required** Name to say hello to. """ ).lstrip(), '\n'.join(output), ) def test_ansi_escape_sequences(self): """Validate that ANSI escape sequences are stripped.""" @click.command(epilog='\033[31mA sample epilog.\033[0m') @click.option( '--name', help='Name to say \033[94mhello\033[0m to.', required=True, type=str, ) @click.option( '--choice', help='A sample option with choices', type=click.Choice(['\033[94mOption1\033[0m', '\033[94mOption2\033[0m']), ) @click.option( '--param', default=lambda: None, show_default='Something computed at \033[94mruntime\033[0m', ) def foobar(): """A sample command with **sparkles**. We've got \033[31mred text\033[0m, \033[104mblue backgrounds\033[0m, a dash of \033[1mbold\033[0m and even some \033[4munderlined words\033[0m. """ pass ctx = click.Context(foobar, info_name='foobar') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command with **sparkles**. We've got red text, blue backgrounds, a dash of bold and even some underlined words. .. program:: foobar .. code-block:: shell foobar [OPTIONS] .. rubric:: Options .. option:: --name **Required** Name to say hello to. .. option:: --choice A sample option with choices :options: Option1 | Option2 .. option:: --param :default: Something computed at runtime A sample epilog. """ ).lstrip(), '\n'.join(output), ) class GroupTestCase(unittest.TestCase): """Validate basic ``click.Group`` instances.""" def test_no_parameters(self): """Validate a `click.Group` with no parameters. This exercises the code paths for a group with *no* arguments, *no* options and *no* environment variables. """ @click.group() def cli(): """A sample command group.""" pass ctx = click.Context(cli, info_name='cli') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... """ ).lstrip(), '\n'.join(output), ) def test_basic_parameters(self): """Validate a combination of parameters. This exercises the code paths for a group with arguments, options and environment variables. """ @click.group() @click.option('--param', envvar='PARAM', help='A sample option') @click.argument('ARG', envvar='ARG') def cli(): """A sample command group.""" pass ctx = click.Context(cli, info_name='cli') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] ARG COMMAND [ARGS]... .. rubric:: Options .. option:: --param A sample option .. rubric:: Arguments .. option:: ARG Required argument .. rubric:: Environment variables .. _cli-param-PARAM: .. envvar:: PARAM :noindex: Provide a default for :option:`--param` .. _cli-arg-ARG: .. envvar:: ARG :noindex: Provide a default for :option:`ARG` """ ).lstrip(), '\n'.join(output), ) def test_no_line_wrapping(self): r"""Validate behavior when a \b character is present. https://click.palletsprojects.com/en/7.x/documentation/#preventing-rewrapping """ @click.group() def cli(): """A sample command group. \b This is a paragraph without rewrapping. And this is a paragraph that will be rewrapped again. """ pass ctx = click.Context(cli, info_name='cli') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command group. | This is | a paragraph | without rewrapping. And this is a paragraph that will be rewrapped again. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... """ ).lstrip(), '\n'.join(output), ) class NestedCommandsTestCase(unittest.TestCase): """Validate ``click.Command`` instances inside ``click.Group`` instances.""" @staticmethod def _get_ctx(): @click.group() def cli(): """A sample command group.""" pass @cli.command() def hello(): """A sample command.""" pass return click.Context(cli, info_name='cli') def test_nested_short(self): """Validate a nested command with 'nested' of 'short' (default). We should list minimal help texts for sub-commands since they're not being handled separately. """ ctx = self._get_ctx() output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... .. rubric:: Commands .. object:: hello A sample command. """ ).lstrip(), '\n'.join(output), ) def test_nested_full(self): """Validate a nested command with 'nested' of 'full'. We should not list sub-commands since they're being handled separately. """ ctx = self._get_ctx() output = list(ext._format_command(ctx, nested='full')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... """ ).lstrip(), '\n'.join(output), ) def test_nested_none(self): """Validate a nested command with 'nested' of 'none'. We should not list sub-commands. """ ctx = self._get_ctx() output = list(ext._format_command(ctx, nested='none')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... """ ).lstrip(), '\n'.join(output), ) class CommandFilterTestCase(unittest.TestCase): """Validate filtering of commands.""" @staticmethod def _get_ctx(): @click.group() def cli(): """A sample command group.""" @cli.command() def hello(): """A sample command.""" @cli.command() def world(): """A world command.""" return click.Context(cli, info_name='cli') def test_no_commands(self): """Validate an empty command group.""" ctx = self._get_ctx() output = list(ext._format_command(ctx, nested='short', commands='')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... """ ).lstrip(), '\n'.join(output), ) def test_order_of_commands(self): """Validate the order of commands.""" ctx = self._get_ctx() output = list(ext._format_command(ctx, nested='short', commands='world, hello')) self.assertEqual( textwrap.dedent( """ A sample command group. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... .. rubric:: Commands .. object:: world A world command. .. object:: hello A sample command. """ ).lstrip(), '\n'.join(output), ) class CustomMultiCommandTestCase(unittest.TestCase): """Validate ``click.MultiCommand`` instances.""" def test_basics(self): """Validate a custom ``click.MultiCommand`` with no parameters. This exercises the code paths to extract commands correctly from these commands. """ @click.command() def hello(): """A sample command.""" @click.command() def world(): """A world command.""" class MyCLI(click.MultiCommand): _command_mapping = { 'hello': hello, 'world': world, } def list_commands(self, ctx): return ['hello', 'world'] def get_command(self, ctx, name): return self._command_mapping[name] cli = MyCLI(help='A sample custom multicommand.') ctx = click.Context(cli, info_name='cli') output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A sample custom multicommand. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... .. rubric:: Commands .. object:: hello A sample command. .. object:: world A world command. """ ).lstrip(), '\n'.join(output), ) def test_hidden(self): """Ensure 'hidden' subcommands are not shown.""" @click.command() def hello(): """A sample command.""" @click.command() def world(): """A world command.""" @click.command(hidden=True) def hidden(): """A hidden command.""" class MyCLI(click.MultiCommand): _command_mapping = { 'hello': hello, 'world': world, 'hidden': hidden, } def list_commands(self, ctx): return ['hello', 'world', 'hidden'] def get_command(self, ctx, name): return self._command_mapping[name] cli = MyCLI(help='A sample custom multicommand.') ctx = click.Context(cli, info_name='cli') output = list(ext._format_command(ctx, nested='short')) # Note that we do NOT expect this to show the 'hidden' command self.assertEqual( textwrap.dedent( """ A sample custom multicommand. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... .. rubric:: Commands .. object:: hello A sample command. .. object:: world A world command. """ ).lstrip(), '\n'.join(output), ) class CommandCollectionTestCase(unittest.TestCase): """Validate ``click.CommandCollection`` instances.""" def test_basics(self): "Validate a ``click.CommandCollection`` with grouped outputs." @click.group() def grp1(): """A first group.""" pass @grp1.command() def hello(): """A hello command.""" @click.group() def grp2(): """A second group.""" pass @grp2.command() def world(): """A world command.""" cli = click.CommandCollection( name='cli', sources=[grp1, grp2], help='A simple CommandCollection.' ) ctx = click.Context(cli, info_name='cli') output = list(ext._format_command(ctx, nested='full')) self.assertEqual( textwrap.dedent( """ A simple CommandCollection. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... """ ).lstrip(), '\n'.join(output), ) output = list(ext._format_command(ctx, nested='short')) self.assertEqual( textwrap.dedent( """ A simple CommandCollection. .. program:: cli .. code-block:: shell cli [OPTIONS] COMMAND [ARGS]... .. rubric:: Commands .. object:: hello A hello command. .. object:: world A world command. """ ).lstrip(), '\n'.join(output), ) sphinx-click-3.0.2/tox.ini000066400000000000000000000012721412534776600154440ustar00rootroot00000000000000[tox] minversion = 2.0 envlist = py{36,37,38,39}-click{7,8},style,docs [testenv] deps = coverage>4.3,<5.0 click7: click>=7.0,<8.0 click8: click>=8.0,<9.0 commands = coverage run --source={toxinidir}/sphinx_click -m unittest discover -s tests/ coverage report pip_pre = pre: true [testenv:coverage] commands = {[testenv]commands} coverage {posargs:html} [testenv:style] deps = flake8 commands = flake8 {toxinidir}/sphinx_click/ [coverage:run] branch = True [testenv:docs] commands = pip install -e {toxinidir}/examples/ sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html [travis] python = 3.6: py36, docs 3.7: py37, style