├── requirements-python2.txt ├── pyproject.toml ├── docs ├── changelog.rst ├── troubleshooting.rst ├── install.rst ├── index.rst ├── usage.rst ├── click-repl.rst ├── Makefile └── conf.py ├── Pipfile ├── click_shell ├── __init__.py ├── decorators.py ├── _compat.py ├── cmd.py └── core.py ├── install.sh ├── sonar-project.properties ├── readthedocs.yaml ├── setup.cfg ├── tests ├── test_shell.py ├── test_core.py └── test_cmd.py ├── CHANGELOG.rst ├── LICENSE ├── setup.py ├── .travis.yml ├── README.rst ├── .gitignore ├── Pipfile.lock └── .pylintrc /requirements-python2.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | pycodestyle 3 | pylint 4 | pytest 5 | pytest-click<1.0 6 | pytest-cov 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta:__legacy__" -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | The changelog is located in GitHub: 5 | 6 | https://github.com/clarkperkins/click-shell/blob/main/CHANGELOG.rst 7 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | click-shell = {editable = true,path = "."} 8 | 9 | [dev-packages] 10 | mypy = "*" 11 | pycodestyle = "*" 12 | pylint = "*" 13 | pytest = "*" 14 | pytest-click = "*" 15 | pytest-cov = "*" 16 | -------------------------------------------------------------------------------- /click_shell/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | click-shell 3 | 4 | An extension to click that easily turns your click app into a shell utility 5 | """ 6 | 7 | from .core import make_click_shell, Shell, ClickShell 8 | from .decorators import shell 9 | 10 | __all__ = [ 11 | 'make_click_shell', 12 | 'shell', 13 | 'Shell', 14 | 'ClickShell', 15 | '__version__', 16 | ] 17 | 18 | __version__ = "3.0.dev0" 19 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then 4 | echo "Python 2 detected, installing dependencies with pip" 5 | pip install -r requirements-python2.txt 6 | else 7 | echo "Installing dependencies with pipenv" 8 | pip install pipenv --upgrade 9 | pipenv install --deploy --dev 10 | fi 11 | 12 | # install the correct version of click for testing 13 | pip install "click~=$CLICK_VERSION" 14 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | 5 | Autocomplete 6 | ------------ 7 | 8 | If autocomplete isn't working after installation, you may be missing the ``readline`` module. 9 | Try one of the following depending on your platform: 10 | 11 | 12 | For macOS / linux (the ``readline`` extra): 13 | 14 | .. code-block:: bash 15 | 16 | pip install click-shell[readline] 17 | 18 | 19 | For Windows / cygwin (the ``windows`` extra): 20 | 21 | .. code-block:: bash 22 | 23 | pip install click-shell[windows] 24 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=clarkperkins_click-shell 2 | # this is the name and version displayed in the SonarCloud UI. 3 | sonar.projectName=click-shell 4 | 5 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 6 | # This property is optional if sonar.modules is set. 7 | sonar.sources=click_shell 8 | 9 | sonar.python.pylint.reportPath=reports/pylint.txt 10 | sonar.python.coverage.reportPaths=reports/coverage.xml 11 | 12 | # Encoding of the source code. Default is default system encoding 13 | sonar.sourceEncoding=UTF-8 14 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The easiest way to install is with pip: 5 | 6 | .. code-block:: bash 7 | 8 | pip install click-shell 9 | 10 | .. warning:: 11 | 12 | Versions of click-shell 3.0.0 and higher no longer support python 2 and click 6. 13 | If you require python 2 or click 6, please remain on click-shell 2.x. 14 | 15 | 16 | If you'd rather, you can clone the github repo and install manually: 17 | 18 | .. code-block:: bash 19 | 20 | git clone https://github.com/clarkperkins/click-shell.git 21 | python setup.py install 22 | -------------------------------------------------------------------------------- /readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | builder: html 11 | configuration: docs/conf.py 12 | 13 | # Build documentation with MkDocs 14 | #mkdocs: 15 | # configuration: mkdocs.yml 16 | 17 | # Optionally build your docs in additional formats such as PDF and ePub 18 | formats: all 19 | 20 | # Optionally set the version of Python and requirements required to build your docs 21 | python: 22 | version: 3.5 23 | 24 | -------------------------------------------------------------------------------- /click_shell/decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | click_shell.decorators 3 | 4 | Decorators to make using click_shell simpler and more similar to click. 5 | """ 6 | 7 | from typing import Any, Callable, Optional, TypeVar, cast 8 | 9 | import click 10 | 11 | from .core import Shell 12 | 13 | F = TypeVar("F", bound=Callable[..., Any]) 14 | 15 | 16 | def shell(name: Optional[str] = None, **attrs: Any) -> Callable[[F], Shell]: 17 | """Creates a new :class:`Shell` with a function as callback. This 18 | works otherwise the same as :func:`command` just that the `cls` 19 | parameter is set to :class:`Shell`. 20 | """ 21 | attrs.setdefault('cls', Shell) 22 | return cast(Shell, click.command(name, **attrs)) 23 | -------------------------------------------------------------------------------- /click_shell/_compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | click_shell._compat 3 | 4 | Compatibility things for readline + various versions of click 5 | """ 6 | 7 | # pylint: disable=unused-import, wrong-import-position 8 | 9 | from typing import List 10 | 11 | import click 12 | 13 | try: 14 | import readline 15 | except ImportError: 16 | try: 17 | import pyreadline as readline 18 | except ImportError: 19 | readline = None 20 | 21 | try: 22 | # Click 8 23 | from click.shell_completion import ShellComplete 24 | 25 | # Wrapper around the new completion system in click 8 26 | def get_choices( 27 | cli: click.BaseCommand, 28 | prog_name: str, 29 | args: List[str], 30 | incomplete: str 31 | ) -> List[str]: 32 | comp = ShellComplete(cli, {}, prog_name, "") 33 | return [c.value for c in comp.get_completions(args, incomplete)] 34 | 35 | except ImportError: 36 | # Click 7 37 | from click._bashcomplete import get_choices 38 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to click-shell's documentation! 2 | ======================================= 3 | 4 | click-shell is an extension to `click`_ that easily turns your click app into a shell utility. 5 | It is built on top of the built in python `cmd`_ module, with modifications to make it work with click. 6 | 7 | click-shell is compatible with python versions 3.6, 3.7, 3.8, and 3.9. 8 | 9 | 10 | Features 11 | -------- 12 | 13 | * Adds a "shell" mode **with command completion** to any click app 14 | * Just a one line change for most click apps 15 | 16 | 17 | .. note:: 18 | 19 | It should be noted that click-shell **only** alters functionality if no arguments are 20 | passed on the command line. Previously if no arguments were passed, the help was displayed. 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | 25 | install 26 | usage 27 | changelog 28 | troubleshooting 29 | click-repl 30 | 31 | 32 | .. _click: https://click.palletsprojects.com/ 33 | .. _cmd: https://docs.python.org/3/library/cmd.html 34 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [pycodestyle] 5 | max_line_length = 100 6 | 7 | [mypy] 8 | ignore_missing_imports = True 9 | 10 | [mypy-click_shell._compat] 11 | ignore_errors = True 12 | 13 | [tool:pytest] 14 | testpaths = tests 15 | 16 | # Coverage settings 17 | [coverage:run] 18 | branch = True 19 | 20 | [coverage:report] 21 | # Regexes for lines to exclude from consideration 22 | exclude_lines = 23 | # Have to re-enable the standard pragma 24 | pragma: no cover 25 | 26 | # Don't complain about missing debug-only code: 27 | def __repr__ 28 | if self\.debug 29 | 30 | # Don't complain if tests don't hit defensive assertion code: 31 | raise AssertionError 32 | raise NotImplementedError 33 | 34 | # Don't complain if non-runnable code isn't run: 35 | if 0: 36 | if __name__ == .__main__.: 37 | 38 | # Don't complain on compatibility code 39 | except ImportError 40 | 41 | ignore_errors = True 42 | 43 | omit = 44 | */site-packages/* 45 | */python*/* 46 | 47 | [coverage:html] 48 | directory = reports/html 49 | title = click-shell coverage 50 | 51 | [coverage:xml] 52 | output = reports/coverage.xml 53 | -------------------------------------------------------------------------------- /tests/test_shell.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | import click_shell 4 | 5 | 6 | # Test shell decorator 7 | def test_shell_decorator(cli_runner): 8 | @click_shell.shell(prompt='app# ') 9 | def app(): 10 | pass 11 | 12 | result = cli_runner.invoke(app) 13 | 14 | assert result.output == 'app# \n' 15 | 16 | 17 | # Test with one command 18 | def test_command_decorator(cli_runner): 19 | @click_shell.shell(prompt='app$ ') 20 | def app_one_command(): 21 | pass 22 | 23 | @app_one_command.command() 24 | def printer(): 25 | click.echo('printed') 26 | 27 | result = cli_runner.invoke(app_one_command, input='printer\n') 28 | 29 | # Verify the context key dictionary 'param' is printed 30 | assert result.output == 'app$ printed\napp$ \n' 31 | 32 | 33 | # Test with finisher 34 | def test_on_finished(cli_runner): 35 | def finisher(ctx): 36 | click.echo(ctx.obj['param']) 37 | 38 | @click_shell.shell(prompt='app> ', on_finished=finisher) 39 | def app_with_finisher(): 40 | pass 41 | 42 | result = cli_runner.invoke(app_with_finisher, obj={'param': 'value'}) 43 | 44 | # Verify the context key dictionary 'param' is printed 45 | assert result.output == 'app> \nvalue\n' 46 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Version 3.0.0 (UNRELEASED) 2 | ------------------------ 3 | 4 | Released TBD 5 | 6 | - Drop support for Python 2 7 | - Drop support for Click 6 8 | 9 | Version 2.1 10 | ----------- 11 | 12 | Released 2021-06-26 13 | 14 | - Drop support for Python 3.5 15 | - Add support for Python 3.9 16 | - Add support for Click 8 17 | 18 | Version 2.0 19 | ----------- 20 | 21 | Released 2020-02-17 22 | 23 | - Drop support for Python 2.6, 3.3, and 3.4 24 | - Add support for Python 3.6, 3.7, and 3.8 25 | - Add support for Click 7 26 | - Added an ``on_finished`` callback method that will get called when the shell exits. 27 | `#16 `_ 28 | - Added support for a changeable prompt. 29 | `#1 `_ 30 | `#18 `_ 31 | - Handle Python installs where ``readline.__doc__`` is None. 32 | `#7 `_ 33 | `#11 `_ 34 | 35 | 36 | Version 1.0 37 | ----------- 38 | 39 | Released 2016-11-28 40 | 41 | - Support ``pyreadline`` for Windows. 42 | `#5 `_ 43 | - Fixed a bug where the command name was passed along to the shell. 44 | `#4 `_ 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Clark Perkins 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of click-shell nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | 4 | from setuptools import setup, find_packages 5 | 6 | 7 | def read(rel_path): 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | with io.open(os.path.join(here, rel_path), 'r') as fp: 10 | return fp.read() 11 | 12 | 13 | def get_version(rel_path): 14 | for line in read(rel_path).splitlines(): 15 | if line.startswith('__version__'): 16 | # __version__ = "0.9" 17 | delim = '"' if '"' in line else "'" 18 | return line.split(delim)[1] 19 | else: 20 | raise RuntimeError("Unable to find version string.") 21 | 22 | 23 | SHORT_DESCRIPTION = "An extension to click that easily turns your click app into a shell utility" 24 | 25 | # Use the README.md as the long description 26 | LONG_DESCRIPTION = read('README.rst') 27 | 28 | requirements = [ 29 | 'click>=7.0', 30 | ] 31 | 32 | setup( 33 | name='click-shell', 34 | version=get_version('click_shell/__init__.py'), 35 | url="https://github.com/clarkperkins/click-shell", 36 | author="Clark Perkins", 37 | author_email="r.clark.perkins@gmail.com", 38 | description=SHORT_DESCRIPTION, 39 | long_description=LONG_DESCRIPTION, 40 | license='BSD', 41 | include_package_data=True, 42 | packages=find_packages(), 43 | zip_safe=False, 44 | install_requires=requirements, 45 | dependency_links=[], 46 | extras_require={ 47 | 'readline': ['gnureadline'], 48 | 'windows': ['pyreadline'], 49 | }, 50 | classifiers=[ 51 | 'Development Status :: 5 - Production/Stable', 52 | 'Intended Audience :: Developers', 53 | 'License :: OSI Approved :: BSD License', 54 | 'Operating System :: OS Independent', 55 | 'Topic :: Software Development :: User Interfaces', 56 | 'Topic :: System :: Shells', 57 | 'Topic :: Utilities', 58 | 'Programming Language :: Python', 59 | 'Programming Language :: Python :: 3', 60 | 'Programming Language :: Python :: 3.6', 61 | 'Programming Language :: Python :: 3.7', 62 | 'Programming Language :: Python :: 3.8', 63 | 'Programming Language :: Python :: 3.9', 64 | ] 65 | ) 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | os: linux 3 | dist: bionic 4 | 5 | python: 6 | - "3.9" 7 | - "3.8" 8 | - "3.7" 9 | - "3.6" 10 | 11 | env: 12 | - CLICK_VERSION="8.0" 13 | - CLICK_VERSION="7.0" 14 | 15 | cache: pip 16 | 17 | git: 18 | depth: false 19 | 20 | branches: 21 | only: 22 | - main 23 | - master 24 | # X.x release branches 25 | - /^[0-9]+\.x$/ 26 | # X.Y.x release branches 27 | - /^[0-9]+\.[0-9]+\.x$/ 28 | # X.Y tags 29 | - /^[0-9]+\.[0-9]+$/ 30 | # X.Y.Z tags 31 | - /^[0-9]+\.[0-9]+\.[0-9]+$/ 32 | 33 | addons: 34 | sonarcloud: 35 | organization: clarkperkins 36 | token: 37 | secure: "thxDw6g+PgwiWrCQ4pgStlU3wbT+Bn06kEHd3DMo1ck5pL+nj9EVwihJp6ToJ7LMV74HjB4Y0A5DREHzS1Uoq8VPWzUXyAAcIXzWxX/GFSDDTJeLqlIE4Z3JBhpMc0bmAkXh6/9vGCQHsNTIi5SpPaw8fs+Fd7jUOAnHsLzTwCU/1G45BHwz/aB7uxhA1XdB4rbGWKBtbxLppH9B7tH2lH1QXriB34BnzoOrcL8xbX231ss3cIINcDq+d13+WWUtR5NynT0g0lUYPXp52202O0Q4xSqpEQ/HCDfcjrBHsn1qguijjbv8pEaa4zpW87nw06VbFVfbY33j4v0AUr57b/K/QBcpI4rRnFk/PAcmkbIDi1luJ0UnhYnhdlFbltbe0qfEO2ZuBc3MGzBpf7lH2UbUfsvNx3AC0/GFaZbVsjluNh+1AZO5vcgGeMPVnS0751/OYhMByp0PpFUlkIQg2vgDAfaF4i/y1h1HF3p1Jvrx7U9BD45ep412LMEeFLGKZ2fqO70Z+xaiIGwyKJjhIMaLDxrSVe4+IshnK4iBltsl4P5vKv0a4REuQed/hhU2YaVX8csz+H4WGq+9+IuN2O3E4shmjzZYAhJj2oYcjv0MXSNpvRBZb0+jr6C1HDEjycPmMcWEj2ByjKYz8HWoDiFVd75ej6WTBUCNt9RyGAg=" 38 | 39 | install: 40 | - ./install.sh 41 | 42 | script: 43 | - ./build.sh 44 | 45 | deploy: 46 | - provider: pypi 47 | user: "__token__" 48 | password: 49 | secure: "rIydgOGZ8IAxTqFLPmrcKQsJSmbSryqyCdIBvvPVsgs5jqUKLR/5XmS8KqX2gZFr5CaMrP85P7olrwt4aQPBCbLn4t3dT+/a1RaScr/fG9Ee5O+dj70ZYTrP02sNupOdEnzgV1S6giHgP/DAfu4UrA6fH/GgtWyyk8l5oV0kajGAceufmuA2sDwjcq4sxZaiV/KY6eFWlwnpYzeVvImXYrHU5mUAxtQvC8oKgk1FKrABCkdfRcXsW7yNmB5tmduSCaMyK2PutN4eBKqbSLWPTORomtt0uAOaVHaIS4p52z15Ug6i9q0FjJwXmxN45wUmS6zElDPYjbJi2tvS6B8gKlrOMbijCe82dDA5LX2NX161mbxeJS24Qvgo+GUhIkQxDFlM8gAuh6Mf19UTvgnPxkw71ubRT7W/43uu5WyZ8IHptg2lw+ss4js7aajHri2h7zS2DIFrTg7LweKHeosiceH+JGoB17KlxmRhwQvAEtW22W3l6oUycrvpSZ0/gy6NyQzgzn4NvaWEUHVIC80AT9/Oq+5TEr9Pr9kDSCeBwgRWmCJ9YIJVILMM69YCBtqEgf6fhTXRQzGv7vaj1yPIKqKuIQh2fUGWjFJN9vIN3mfWmwqAamg2PTZrK1GTDfAA6JDYIjyZeE5GNnjpd66sKEefsGwKfrrgxhFTF6mksmU=" 50 | distributions: sdist bdist_wheel 51 | on: 52 | tags: true 53 | repo: clarkperkins/click-shell 54 | python: '3.9' 55 | env: CLICK_VERSION="8.0" 56 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | click-shell 2 | =========== 3 | 4 | |TravisCI| |Sonar| |DocsStatus| 5 | 6 | click-shell is an extension to `click`_ that easily turns your click app into a shell utility. 7 | It is built on top of the built in python `cmd`_ module, with modifications to make it work with click. 8 | 9 | 10 | Features 11 | -------- 12 | 13 | * Adds a "shell" mode **with command completion** to any click app 14 | * Just a one line change for most click apps 15 | 16 | 17 | Usage 18 | ----- 19 | 20 | Simply replace ``@click.group`` with ``@click_shell.shell`` on the root level command: 21 | 22 | 23 | .. code-block:: python 24 | 25 | import click 26 | from click_shell import shell 27 | 28 | # @click.group() # no longer 29 | @shell(prompt='my-app > ', intro='Starting my app...') 30 | def my_app(): 31 | pass 32 | 33 | @my_app.command() 34 | def testcommand(): 35 | click.echo("testcommand is running") 36 | 37 | # more commands 38 | 39 | if __name__ == '__main__': 40 | my_app() 41 | 42 | 43 | When run, you should expect an output like so: 44 | 45 | .. code-block:: bash 46 | 47 | $ python my_app.py 48 | Starting my app... 49 | my-app > testcommand 50 | testcommand is running 51 | my-app > 52 | 53 | 54 | .. note:: 55 | 56 | It should be noted that this decorator **only** alters functionality if no arguments are 57 | passed on the command line. If you try to run a command directly 58 | (like ``python my_app.py the_command`` in the above example), your app will function 59 | identically to how it did before. 60 | 61 | 62 | For more advanced usage, check out our docs at https://click-shell.readthedocs.io/ 63 | 64 | .. note:: 65 | 66 | Versions of click-shell 3.0.0 and higher no longer support python 2 and click 6. 67 | If you require python 2 or click 6, please remain on click-shell 2.x. 68 | 69 | .. _click: http://click.pocoo.org/ 70 | .. _cmd: https://docs.python.org/2/library/cmd.html 71 | 72 | .. |TravisCI| image:: https://travis-ci.com/clarkperkins/click-shell.svg?branch=main 73 | :target: https://travis-ci.com/clarkperkins/click-shell 74 | :alt: Build status 75 | 76 | .. |DocsStatus| image:: https://readthedocs.org/projects/click-shell/badge/?version=latest 77 | :target: https://click-shell.readthedocs.io/en/latest/?badge=latest 78 | :alt: Documentation Status 79 | 80 | .. |Sonar| image:: https://sonarcloud.io/api/project_badges/measure?project=clarkperkins_click-shell&metric=alert_status 81 | :target: https://sonarcloud.io/dashboard?id=clarkperkins_click-shell 82 | :alt: Quality Gate Status 83 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime 3 | 4 | import click 5 | import pytest 6 | 7 | from click_shell.cmd import ClickCmd 8 | from click_shell.core import Shell, make_click_shell, get_invoke 9 | 10 | 11 | class TestShell: 12 | 13 | def test_create(self): 14 | 15 | shell = Shell() 16 | 17 | assert isinstance(shell, click.MultiCommand) 18 | 19 | def test_invoke_no_args(self): 20 | shell = Shell() 21 | 22 | ctx = shell.make_context('click-shell-test', args=[]) 23 | 24 | with pytest.raises(IOError): 25 | shell.invoke(ctx) 26 | 27 | def test_invoke_with_args(self): 28 | shell = Shell() 29 | 30 | # Create a 'foo' command 31 | @shell.command() 32 | def foo(): 33 | click.echo('bar') 34 | return 0 35 | 36 | ctx = shell.make_context('click-shell-test', args=['foo']) 37 | 38 | retcode = shell.invoke(ctx) 39 | 40 | assert retcode == 0 41 | 42 | 43 | class TestFactory: 44 | 45 | def test_fail_on_bad_command(self): 46 | command = click.Command('test-command') 47 | 48 | ctx = click.Context(command) 49 | 50 | with pytest.raises(AssertionError): 51 | make_click_shell(ctx) 52 | 53 | def test_with_group(self): 54 | command = click.Group() 55 | 56 | ctx = click.Context(command) 57 | 58 | shell = make_click_shell(ctx) 59 | 60 | assert isinstance(shell, ClickCmd) 61 | 62 | def test_get_invoke_command(self): 63 | time_str = str(datetime.now()) 64 | 65 | @click.command() 66 | def test_command(): 67 | click.echo(time_str) 68 | return time_str 69 | 70 | fun = get_invoke(test_command) 71 | 72 | assert callable(fun) 73 | 74 | ret = fun(None, '') 75 | 76 | # Make sure it returned the correct thing 77 | assert ret is False 78 | 79 | def test_get_invoke_group(self): 80 | time_str = str(datetime.now()) 81 | 82 | @click.group(invoke_without_command=True) 83 | def main_level(): 84 | pass 85 | 86 | @main_level.group() 87 | def test_group(): 88 | pass 89 | 90 | @test_group.command() 91 | def foo(): 92 | click.echo(time_str) 93 | return time_str 94 | 95 | @test_group.command() 96 | def bar(): 97 | click.echo('foo') 98 | return 'foo' 99 | 100 | fun = get_invoke(test_group) 101 | 102 | assert callable(fun) 103 | 104 | # This should be the help function 105 | ret = fun(None, '') 106 | assert ret is False 107 | 108 | # Also help 109 | ret = fun(None, '--help') 110 | assert ret is False 111 | 112 | # non-existant 113 | ret = fun(None, 'foobar') 114 | assert ret is False 115 | 116 | ret = fun(None, 'foo') 117 | assert ret is False 118 | 119 | ret = fun(None, 'bar') 120 | assert ret is False 121 | 122 | def test_get_help(self): 123 | pass 124 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | There are 2 main ways to utilize click-shell: the decorator and the factory method. 5 | 6 | Decorator 7 | --------- 8 | 9 | The easiest way to get going with click-shell is with the click style decorator. 10 | ``@click_shell.shell`` is meant to replace click's ``@click.group`` decorator for the root level 11 | of your app. In fact, the object generated by ``@click_shell.shell`` is a 12 | ``click_shell.core.Shell`` object, which is a subclass of ``click.core.Group``. 13 | 14 | .. code-block:: python 15 | 16 | from click_shell import shell 17 | 18 | # @click.group() # no longer 19 | @shell(prompt='my-app > ', intro='Starting my app...') 20 | def my_app(): 21 | pass 22 | 23 | @my_app.command() 24 | def testcommand(): 25 | print('testcommand is running') 26 | 27 | # more commands 28 | 29 | if __name__ == '__main__': 30 | my_app() 31 | 32 | 33 | When run with the above arguments, you should expect an output like so: 34 | 35 | .. code-block:: bash 36 | 37 | $ python my_app.py 38 | Starting my app... 39 | my-app > testcommand 40 | testcommand is running 41 | my-app > 42 | 43 | 44 | ``@shell`` takes 4 arguments: 45 | 46 | - ``prompt`` - this will get printed as the beginning of each line in the shell. 47 | This can take a callable that will be called each time a prompt is printed. 48 | On Python 3 ONLY, if the callable takes an argument named ``ctx``, 49 | the click context will be passed in as that argument. 50 | Defaults to ``'(Cmd) '`` 51 | - ``intro`` - this will get printed once when the shell first starts 52 | Defaults to ``None``, meaning nothing gets printed 53 | - ``hist_file`` - this is the location of the history file used by the shell. 54 | Defaults to ``'~/.click-history'`` 55 | - ``on_finished`` - a callable that will be called when the shell exits. 56 | You can use it to clean up any resources that may need cleaning up. 57 | - ``shell_cls`` - a subclass of ``click_shell.ClickShell`` used to build the shell. 58 | You can use this to add custom implementations of methods from the python ``Cmd`` class. 59 | 60 | ``@shell`` also takes arbitrary keyword arguments, and they are passed on directly to the 61 | constructor for the ``click_shell.Shell`` class (and therefore passed along to the 62 | ``click.Group`` constructor, as ``click_shell.Shell`` inherits from ``click.Group``). 63 | 64 | 65 | Factory Method 66 | -------------- 67 | 68 | If you'd rather not use decorators (or can't for some reason), you can manually create a shell 69 | object and start it up: 70 | 71 | 72 | .. code-block:: python 73 | 74 | import click 75 | from click_shell import make_click_shell 76 | 77 | @click.group() 78 | @click.pass_context 79 | def my_app(ctx): 80 | pass 81 | 82 | 83 | 84 | # Somewhere else in your code (as long as you have access to the root level Context object) 85 | 86 | shell = make_click_shell(ctx, prompt='my-app > ', intro='Starting my app...') 87 | shell.cmdloop() 88 | 89 | 90 | The first argument passed to ``make_click_shell`` must be the root level context object for 91 | your click application. The other 3 args (prompt, intro, hist_file, shell_cls) are the same as described 92 | above under the Decorator section. 93 | -------------------------------------------------------------------------------- /docs/click-repl.rst: -------------------------------------------------------------------------------- 1 | Differences with click-repl 2 | =========================== 3 | 4 | `Click-repl`_ is another Click extension that provides a feature similar 5 | to click-shell. This page lists the main differences between the two 6 | extensions. 7 | 8 | 9 | Click-shell sets the shell as the default subcommand 10 | ---------------------------------------------------- 11 | 12 | With click-shell, the interactive shell is automatically started if the 13 | Click application is invoked without an explicit subcommand. 14 | 15 | By contrast, click-repl adds a new subcommand to start the shell (named 16 | ``repl`` by default), but that command is *not* used by default. If the 17 | program is invoked without a subcommand, the help message is displayed 18 | (as with standard Click). The user has to explicitly invoke the ``repl`` 19 | command to get the interactive shell. 20 | 21 | 22 | Click-shell calls the root function only once 23 | --------------------------------------------- 24 | 25 | This is possibly the most important difference, depending on how your 26 | Click application is designed. 27 | 28 | To understand it, consider the following minimal example with 29 | click-shell: 30 | 31 | .. code-block:: python 32 | 33 | import click 34 | from click_shell import shell 35 | 36 | @shell(prompt='my-app > ') 37 | def my_app(): 38 | print('initializing the application...') 39 | 40 | @my_app.command() 41 | def testcommand(): 42 | print('testcommand is running') 43 | 44 | # more commands... 45 | 46 | if __name__ == '__main__': 47 | my_app() 48 | 49 | The code inside the ``my_app`` function will always be called only once, 50 | just before the interactive shell is started: 51 | 52 | .. code-block:: bash 53 | 54 | $ python my_app.py 55 | initializing the application... 56 | my-app > testcommand 57 | testcommand is running 58 | my-app > testcommand 59 | testcommand is running 60 | my-app > 61 | 62 | 63 | By contrast, with the equivalent code using click-repl: 64 | 65 | .. code-block:: python 66 | 67 | import click 68 | from click_repl import register_repl 69 | 70 | @click.group() 71 | def my_app(): 72 | print('initializing the application...') 73 | 74 | @my_app.command() 75 | def testcommand(): 76 | print('testcommand is running') 77 | 78 | # more commands... 79 | 80 | if __name__ == '__main__': 81 | register_repl(my_app) 82 | my_app() 83 | 84 | The code inside the ``my_app`` function will be executed when the 85 | application is started *and* before the invocation of any subcommand 86 | from the interactive shell: 87 | 88 | .. code-block:: bash 89 | 90 | $ python my_app.py repl 91 | initializing the application... 92 | > testcommand 93 | initializing the application... 94 | testcommand is running 95 | > testcommand 96 | initializing the application... 97 | testcommand is running 98 | > 99 | 100 | Obviously that difference in behaviour does not matter if the "root" 101 | function of your Click app (``my_app`` in those examples) does nothing. 102 | But if that function does anything meaningful (such as performing some 103 | initialization steps, reading a configuration file, etc.), then you must 104 | be aware of that difference. With click-repl, you need to make sure it 105 | is acceptable for the code in your root function to be called repeatedly 106 | over the lifetime of the application. 107 | 108 | 109 | Click-shell has no shell escape 110 | ------------------------------- 111 | 112 | The interactive shell created by click-repl allows the user to invoke 113 | commands from the underlying system shell in addition to the commands 114 | from the application itself, by prefixing them with ``!``. 115 | 116 | Click-shell has no such feature. 117 | 118 | .. _click-repl: https://github.com/click-contrib/click-repl 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | ### macOS template 133 | # General 134 | .DS_Store 135 | .AppleDouble 136 | .LSOverride 137 | 138 | # Icon must end with two \r 139 | Icon 140 | 141 | # Thumbnails 142 | ._* 143 | 144 | # Files that might appear in the root of a volume 145 | .DocumentRevisions-V100 146 | .fseventsd 147 | .Spotlight-V100 148 | .TemporaryItems 149 | .Trashes 150 | .VolumeIcon.icns 151 | .com.apple.timemachine.donotpresent 152 | 153 | # Directories potentially created on remote AFP share 154 | .AppleDB 155 | .AppleDesktop 156 | Network Trash Folder 157 | Temporary Items 158 | .apdisk 159 | 160 | 161 | ### JetBrains template 162 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 163 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 164 | 165 | .idea/ 166 | 167 | 168 | # CMake 169 | cmake-build-*/ 170 | 171 | # File-based project format 172 | *.iws 173 | 174 | # IntelliJ 175 | out/ 176 | 177 | # mpeltonen/sbt-idea plugin 178 | .idea_modules/ 179 | 180 | # JIRA plugin 181 | atlassian-ide-plugin.xml 182 | 183 | 184 | # Crashlytics plugin (for Android Studio and IntelliJ) 185 | com_crashlytics_export_strings.xml 186 | crashlytics.properties 187 | crashlytics-build.properties 188 | fabric.properties 189 | 190 | ### Vim template 191 | # Swap 192 | [._]*.s[a-v][a-z] 193 | !*.svg # comment out if you don't need vector files 194 | [._]*.sw[a-p] 195 | [._]s[a-rt-v][a-z] 196 | [._]ss[a-gi-z] 197 | [._]sw[a-p] 198 | 199 | # Session 200 | Session.vim 201 | Sessionx.vim 202 | 203 | # Temporary 204 | .netrwhist 205 | *~ 206 | # Auto-generated tag files 207 | tags 208 | # Persistent undo 209 | [._]*.un~ 210 | 211 | ### Extras 212 | .history 213 | reports/ 214 | -------------------------------------------------------------------------------- /click_shell/cmd.py: -------------------------------------------------------------------------------- 1 | """ 2 | click_shell._cmd 3 | 4 | This module overrides the builtin python cmd module 5 | """ 6 | 7 | import inspect 8 | import os 9 | from cmd import Cmd 10 | from typing import Any, Callable, List, Optional, Union 11 | 12 | import click 13 | 14 | from ._compat import readline 15 | 16 | 17 | class ClickCmd(Cmd): 18 | """ 19 | A simple wrapper around the builtin python cmd module that: 20 | 1) makes completion work on OSX 21 | 2) uses a history file 22 | 3) uses click.echo instead of std*.write() 23 | """ 24 | 25 | # Allow dashes 26 | identchars = Cmd.identchars + '-' 27 | 28 | nohelp = "No help on %s" 29 | nocommand = "Command not found: %s" 30 | 31 | prompt: Optional[Union[str, Callable[..., str]]] = Cmd.prompt # type: ignore 32 | 33 | def __init__( 34 | self, 35 | ctx: Optional[click.Context] = None, 36 | hist_file: Optional[str] = None, 37 | *args, 38 | **kwargs, 39 | ): 40 | # Never allow super() to default to sys.stdout for stdout. 41 | # Instead pass along a wrapper that delegates to click.echo(). 42 | self._stdout = kwargs.get('stdout') 43 | 44 | super().__init__(*args, **kwargs) 45 | 46 | self.old_completer: Optional[Callable] = None 47 | self.old_delims: Optional[str] = None 48 | 49 | # We need to save the context!! 50 | self.ctx: Optional[click.Context] = ctx 51 | 52 | # Set the history file 53 | hist_file = hist_file or os.path.join(os.path.expanduser('~'), '.click-history') 54 | self.hist_file: str = os.path.abspath(hist_file) 55 | 56 | # Make the parent directory 57 | if not os.path.isdir(os.path.dirname(self.hist_file)): 58 | os.makedirs(os.path.dirname(self.hist_file)) 59 | 60 | def preloop(self): 61 | # read our history 62 | if readline: 63 | try: 64 | readline.read_history_file(self.hist_file) 65 | except IOError: 66 | pass 67 | 68 | def postloop(self): 69 | # Write our history 70 | if readline: 71 | readline.set_history_length(1000) 72 | try: 73 | readline.write_history_file(self.hist_file) 74 | except IOError: 75 | pass 76 | 77 | # We need to override this to fix readline 78 | def cmdloop(self, intro: str = None): # pylint: disable=too-many-branches 79 | self.preloop() 80 | if self.completekey and readline: 81 | self.old_completer = readline.get_completer() 82 | self.old_delims = readline.get_completer_delims() 83 | readline.set_completer(self.complete) # type: ignore 84 | readline.set_completer_delims(' \n\t') 85 | to_parse = self.completekey + ': complete' 86 | if readline.__doc__ and 'libedit' in readline.__doc__: 87 | # Special case for mac OSX 88 | to_parse = 'bind ^I rl_complete' 89 | readline.parse_and_bind(to_parse) 90 | try: 91 | if intro is not None: 92 | self.intro = intro 93 | if self.intro: 94 | click.echo(self.intro, file=self._stdout) 95 | stop = None 96 | while not stop: 97 | if self.cmdqueue: 98 | line = self.cmdqueue.pop(0) 99 | else: 100 | try: 101 | line = input(self.get_prompt()) 102 | except EOFError: 103 | # We just want to quit here instead of changing the arg to EOF 104 | click.echo(file=self._stdout) 105 | break 106 | except KeyboardInterrupt: 107 | # We don't want to exit the shell on a keyboard interrupt 108 | click.echo(file=self._stdout) 109 | click.echo('KeyboardInterrupt', file=self._stdout) 110 | continue 111 | line = self.precmd(line) 112 | stop = self.onecmd(line) 113 | stop = self.postcmd(stop, line) 114 | 115 | finally: 116 | self.postloop() 117 | if self.completekey and readline: 118 | if self.old_completer: 119 | readline.set_completer(self.old_completer) 120 | if self.old_delims: 121 | readline.set_completer_delims(self.old_delims) 122 | 123 | def get_prompt(self) -> Optional[str]: 124 | if callable(self.prompt): 125 | kwargs = {} 126 | if hasattr(inspect, 'signature'): 127 | sig = inspect.signature(self.prompt) 128 | if 'ctx' in sig.parameters: 129 | kwargs['ctx'] = self.ctx 130 | return self.prompt(**kwargs) 131 | else: 132 | return self.prompt 133 | 134 | def emptyline(self) -> bool: 135 | # we don't want to repeat the last command if nothing was typed 136 | return False 137 | 138 | def default(self, line: str): 139 | click.echo(self.nocommand % line, file=self._stdout) 140 | 141 | def get_names(self) -> List[str]: 142 | # Do dir(self) instead of dir(self.__class__) 143 | return dir(self) 144 | 145 | def do_help(self, arg: str): 146 | if not arg: 147 | super().do_help(arg) 148 | return 149 | 150 | # Override to give better error message 151 | try: 152 | func = getattr(self, 'help_' + arg) 153 | except AttributeError: 154 | try: 155 | do_fun = getattr(self, 'do_' + arg, None) 156 | 157 | if do_fun is None: 158 | click.echo(self.nocommand % arg, file=self._stdout) 159 | return 160 | 161 | doc = do_fun.__doc__ 162 | if doc: 163 | click.echo(doc, file=self._stdout) 164 | return 165 | except AttributeError: 166 | pass 167 | click.echo(self.nohelp % arg, file=self._stdout) 168 | return 169 | func() 170 | 171 | def do_quit(self, arg) -> bool: # pylint: disable=unused-argument,no-self-use 172 | return True 173 | 174 | def do_exit(self, arg) -> bool: # pylint: disable=unused-argument,no-self-use 175 | return True 176 | 177 | def print_topics(self, header: Any, cmds: Optional[List[str]], cmdlen: int, maxcol: int): 178 | if cmds: 179 | click.echo(header, file=self._stdout) 180 | if self.ruler: 181 | click.echo(str(self.ruler * len(header)), file=self._stdout) 182 | self.columnize(cmds, maxcol - 1) 183 | click.echo(file=self._stdout) 184 | -------------------------------------------------------------------------------- /click_shell/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | click_shell.core 3 | 4 | Core functionality for click-shell 5 | """ 6 | 7 | import logging 8 | import shlex 9 | import traceback 10 | import types 11 | from functools import update_wrapper 12 | from logging import NullHandler 13 | from typing import Any, Callable, List, Optional, Type, Union 14 | 15 | import click 16 | 17 | from ._compat import get_choices 18 | from .cmd import ClickCmd 19 | 20 | logger = logging.getLogger(__name__) 21 | logger.addHandler(NullHandler()) 22 | 23 | 24 | def get_invoke(command: click.Command) -> Callable[[ClickCmd, str], bool]: 25 | """ 26 | Get the Cmd main method from the click command 27 | :param command: The click Command object 28 | :return: the do_* method for Cmd 29 | :rtype: function 30 | """ 31 | 32 | assert isinstance(command, click.Command) 33 | assert command.callback 34 | 35 | def invoke_(self: ClickCmd, arg: str): # pylint: disable=unused-argument 36 | try: 37 | command.main(args=shlex.split(arg), 38 | prog_name=command.name, 39 | standalone_mode=False, 40 | parent=self.ctx) 41 | except click.ClickException as e: 42 | # Show the error message 43 | e.show() 44 | except click.Abort: 45 | # We got an EOF or Keyboard interrupt. Just silence it 46 | pass 47 | except SystemExit: 48 | # Catch this an return the code instead. All of click's help commands do a sys.exit(), 49 | # and that's not ideal when running in a shell. 50 | pass 51 | except Exception as e: 52 | traceback.print_exception(type(e), e, None) 53 | logger.warning(traceback.format_exc()) 54 | 55 | # Always return False so the shell doesn't exit 56 | return False 57 | 58 | invoke_ = update_wrapper(invoke_, command.callback) 59 | invoke_.__name__ = 'do_%s' % command.name 60 | return invoke_ 61 | 62 | 63 | def get_help(command: click.Command) -> Callable[[ClickCmd], None]: 64 | """ 65 | Get the Cmd help function from the click command 66 | :param command: The click Command object 67 | :return: the help_* method for Cmd 68 | :rtype: function 69 | """ 70 | assert isinstance(command, click.Command) 71 | 72 | def help_(self: ClickCmd): # pylint: disable=unused-argument 73 | extra = {} 74 | for key, value in command.context_settings.items(): 75 | if key not in extra: 76 | extra[key] = value 77 | 78 | # Print click's help message 79 | with click.Context(command, info_name=command.name, parent=self.ctx, **extra) as ctx: 80 | click.echo(ctx.get_help(), color=ctx.color) 81 | 82 | help_.__name__ = 'help_%s' % command.name 83 | return help_ 84 | 85 | 86 | def get_complete(command: click.Command) -> Callable[[ClickCmd, str, str, int, int], List[str]]: 87 | """ 88 | Get the Cmd complete function for the click command 89 | :param command: The click Command object 90 | :return: the complete_* method for Cmd 91 | :rtype: function 92 | """ 93 | 94 | assert isinstance(command, click.Command) 95 | 96 | # pylint: disable=unused-argument 97 | def complete_( 98 | self: ClickCmd, 99 | text: str, 100 | line: str, 101 | begidx: int, 102 | endidx: int, 103 | ): 104 | # Parse the args 105 | args = shlex.split(line[:begidx]) 106 | # Strip of the first item which is the name of the command 107 | args = args[1:] 108 | 109 | # Then pass them on to the get_choices method that click uses for completion 110 | return [choice[0] if isinstance(choice, tuple) else choice 111 | for choice in get_choices(command, command.name or "", args, text)] 112 | 113 | complete_.__name__ = 'complete_%s' % command.name 114 | return complete_ 115 | 116 | 117 | class ClickShell(ClickCmd): 118 | 119 | def add_command(self, cmd: click.Command, name: str): 120 | # Use the MethodType to add these as bound methods to our current instance 121 | setattr(self, 'do_%s' % name, types.MethodType(get_invoke(cmd), self)) 122 | setattr(self, 'help_%s' % name, types.MethodType(get_help(cmd), self)) 123 | setattr(self, 'complete_%s' % name, types.MethodType(get_complete(cmd), self)) 124 | 125 | 126 | def make_click_shell( 127 | ctx: click.Command, 128 | prompt: Optional[Union[str, Callable[[], str], Callable[[click.Context, str], str]]] = None, 129 | intro: Optional[str] = None, 130 | hist_file: Optional[str] = None, 131 | shell_cls: Type[ClickShell] = ClickShell, 132 | ) -> ClickShell: 133 | assert isinstance(ctx, click.Context) 134 | assert isinstance(ctx.command, click.MultiCommand) 135 | 136 | # Set this to None so that it doesn't get printed out in usage messages 137 | ctx.info_name = None 138 | 139 | # Create our shell object 140 | shell = shell_cls(ctx=ctx, hist_file=hist_file) 141 | 142 | if prompt is not None: 143 | shell.prompt = prompt 144 | 145 | if intro is not None: 146 | shell.intro = intro 147 | 148 | # Add all the commands 149 | for name in ctx.command.list_commands(ctx): 150 | command = ctx.command.get_command(ctx, name) 151 | shell.add_command(command, name) 152 | 153 | return shell 154 | 155 | 156 | class Shell(click.Group): 157 | 158 | def __init__( 159 | self, 160 | prompt: Optional[Union[str, Callable[..., str]]] = None, 161 | intro: Optional[str] = None, 162 | hist_file: Optional[str] = None, 163 | on_finished: Optional[Callable[[click.Context], None]] = None, 164 | shell_cls: Type[ClickShell] = ClickShell, 165 | **attrs 166 | ): 167 | attrs['invoke_without_command'] = True 168 | super().__init__(**attrs) 169 | 170 | # Make our shell 171 | self.shell: ClickShell = shell_cls(hist_file=hist_file) 172 | self.on_finished: Optional[Callable[[click.Context], None]] = on_finished 173 | if prompt: 174 | self.shell.prompt = prompt 175 | self.shell.intro = intro 176 | 177 | def add_command(self, cmd: click.Command, name: Optional[str] = None): 178 | super().add_command(cmd, name) 179 | 180 | # Grab the proper name 181 | name = name or cmd.name 182 | 183 | assert name 184 | 185 | # Add the command to the shell 186 | self.shell.add_command(cmd, name) 187 | 188 | def invoke(self, ctx: click.Context) -> Any: 189 | # Call super() first. This ensures that we call the method body of our instance first, 190 | # in case it's something other than `pass` 191 | ret = super().invoke(ctx) 192 | 193 | try: 194 | if not ctx.protected_args and not ctx.invoked_subcommand: 195 | # Set this to None so that it doesn't get printed out in usage messages 196 | ctx.info_name = None 197 | 198 | # Set the context on the shell 199 | self.shell.ctx = ctx 200 | 201 | # Start up the shell 202 | self.shell.cmdloop() 203 | finally: 204 | # Finisher callback on the context 205 | if self.on_finished: 206 | self.on_finished(ctx) 207 | 208 | return ret 209 | -------------------------------------------------------------------------------- /tests/test_cmd.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import sys 4 | from cmd import Cmd 5 | 6 | import click 7 | 8 | from click_shell.cmd import ClickCmd 9 | from click_shell.core import ClickShell 10 | 11 | 12 | class BadStringIO(io.StringIO): 13 | def __init__(self, *args, **kwargs): 14 | super(BadStringIO, self).__init__(*args, **kwargs) 15 | self.first = True 16 | 17 | def read(self, *args, **kwargs): 18 | if self.first: 19 | self.first = False 20 | raise KeyboardInterrupt() 21 | return super(BadStringIO, self).read(*args, **kwargs) 22 | 23 | def readline(self, *args, **kwargs): 24 | if self.first: 25 | self.first = False 26 | raise KeyboardInterrupt() 27 | return super(BadStringIO, self).readline(*args, **kwargs) 28 | 29 | 30 | def test_create(): 31 | cmd = ClickCmd() 32 | 33 | # Make sure it's a Cmd 34 | assert isinstance(cmd, Cmd) 35 | 36 | # Make sure we have our exit functions 37 | assert hasattr(cmd, 'do_quit') 38 | assert hasattr(cmd, 'do_exit') 39 | 40 | 41 | def test_intro_default(cli_runner): 42 | with cli_runner.isolation() as outstreams: 43 | cmd = ClickCmd(hist_file='.history') 44 | 45 | cmd.cmdloop() 46 | 47 | output = outstreams[0].getvalue() \ 48 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 49 | 50 | assert output == '(Cmd) \n' 51 | os.remove('.history') 52 | 53 | 54 | def test_intro_custom(cli_runner): 55 | for test_in in ('foo', 'bar', 'blah\n version 2'): 56 | with cli_runner.isolation() as outstreams: 57 | cmd = ClickCmd(hist_file='.history') 58 | cmd.cmdloop(test_in) 59 | 60 | output = outstreams[0].getvalue() \ 61 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 62 | 63 | assert output == '{0}\n(Cmd) \n'.format(test_in) 64 | 65 | os.remove('.history') 66 | 67 | 68 | def test_prompt(cli_runner): 69 | with cli_runner.isolation() as outstreams: 70 | cmd = ClickCmd(hist_file='.history') 71 | 72 | cmd.prompt = 'foobar > ' 73 | 74 | cmd.cmdloop() 75 | 76 | output = outstreams[0].getvalue() \ 77 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 78 | 79 | assert output == 'foobar > \n' 80 | 81 | os.remove('.history') 82 | 83 | 84 | def test_bad_input(cli_runner): 85 | with cli_runner.isolation(input='foobar\n') as outstreams: 86 | cmd = ClickCmd(hist_file='.history') 87 | 88 | cmd.cmdloop() 89 | 90 | output = outstreams[0].getvalue() \ 91 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 92 | 93 | assert output == '{0}{1}\n{0}\n'.format(ClickCmd.prompt, 94 | ClickCmd.nocommand % 'foobar') 95 | 96 | os.remove('.history') 97 | 98 | 99 | def test_empty_input(cli_runner): 100 | with cli_runner.isolation(input='\n') as outstreams: 101 | cmd = ClickCmd(hist_file='.history') 102 | 103 | cmd.cmdloop() 104 | 105 | output = outstreams[0].getvalue() \ 106 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 107 | 108 | assert output == '{0}{0}\n'.format(ClickCmd.prompt) 109 | 110 | os.remove('.history') 111 | 112 | 113 | def test_quit(cli_runner): 114 | with cli_runner.isolation(input='quit\n') as outstreams: 115 | cmd = ClickCmd(hist_file='.history') 116 | 117 | cmd.cmdloop() 118 | 119 | output = outstreams[0].getvalue() \ 120 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 121 | 122 | assert output == ClickCmd.prompt 123 | 124 | os.remove('.history') 125 | 126 | 127 | def test_exit(cli_runner): 128 | with cli_runner.isolation(input='exit\n') as outstreams: 129 | cmd = ClickCmd(hist_file='.history') 130 | 131 | cmd.cmdloop() 132 | 133 | output = outstreams[0].getvalue() \ 134 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 135 | 136 | assert output == ClickCmd.prompt 137 | 138 | os.remove('.history') 139 | 140 | 141 | def test_root_help_basic(cli_runner): 142 | with cli_runner.isolation(input='help\n') as outstreams: 143 | cmd = ClickCmd(hist_file='.history') 144 | 145 | cmd.cmdloop() 146 | 147 | output = outstreams[0].getvalue() \ 148 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 149 | 150 | assert output == '{0}\n' \ 151 | 'Undocumented commands:\n' \ 152 | '======================\n' \ 153 | 'exit help quit\n' \ 154 | '\n' \ 155 | '{0}\n'.format(ClickCmd.prompt) 156 | 157 | os.remove('.history') 158 | 159 | 160 | @click.command() 161 | def sample(): 162 | """ 163 | Sample command 164 | """ 165 | 166 | 167 | def test_root_help_with_command(cli_runner): 168 | with cli_runner.isolation(input='help\n') as outstreams: 169 | ctx = click.Context(sample) 170 | 171 | cmd = ClickShell(ctx, hist_file='.history') 172 | cmd.add_command(sample, sample.name) 173 | 174 | cmd.cmdloop() 175 | 176 | output = outstreams[0].getvalue() \ 177 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 178 | 179 | assert output == '{0}\n' \ 180 | 'Documented commands (type help ):\n' \ 181 | '========================================\n' \ 182 | 'sample\n' \ 183 | '\n' \ 184 | 'Undocumented commands:\n' \ 185 | '======================\n' \ 186 | 'exit help quit\n' \ 187 | '\n' \ 188 | '{0}\n'.format(ClickCmd.prompt) 189 | 190 | os.remove('.history') 191 | 192 | 193 | def test_command_help(cli_runner): 194 | with cli_runner.isolation(input='help sample\n') as outstreams: 195 | ctx = click.Context(sample) 196 | 197 | cmd = ClickShell(ctx, hist_file='.history') 198 | cmd.add_command(sample, sample.name) 199 | 200 | cmd.cmdloop() 201 | 202 | output = outstreams[0].getvalue() \ 203 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 204 | 205 | assert output == '{0}' \ 206 | 'Usage: sample [OPTIONS]\n' \ 207 | '\n' \ 208 | ' Sample command\n' \ 209 | '\n' \ 210 | 'Options:\n' \ 211 | ' --help Show this message and exit.\n' \ 212 | '{0}\n'.format(ClickCmd.prompt) 213 | 214 | os.remove('.history') 215 | 216 | 217 | def test_keyboard_interrupt(): 218 | stdin = BadStringIO() 219 | stdout = io.StringIO() 220 | 221 | old_in = sys.stdin 222 | old_out = sys.stdout 223 | try: 224 | sys.stdin = stdin 225 | sys.stdout = stdout 226 | 227 | cmd = ClickCmd(hist_file='.history') 228 | 229 | cmd.cmdloop() 230 | finally: 231 | sys.stdin = old_in 232 | sys.stdout = old_out 233 | 234 | assert stdout.getvalue() == '{0}\nKeyboardInterrupt\n{0}\n'.format(ClickCmd.prompt) 235 | 236 | os.remove('.history') 237 | 238 | 239 | def test_changable_prompt(cli_runner): 240 | with cli_runner.isolation(input='\n\n\n') as outstreams: 241 | 242 | cmd = ClickCmd(hist_file='.history') 243 | 244 | class Prompt: 245 | 246 | def __init__(self): 247 | self.num = 0 248 | 249 | def __call__(self): 250 | self.num += 1 251 | return "prompt #{} > ".format(self.num) 252 | 253 | cmd.prompt = Prompt() 254 | 255 | cmd.cmdloop() 256 | 257 | output = outstreams[0].getvalue() \ 258 | .decode(cli_runner.charset, 'replace').replace('\r\n', '\n') 259 | 260 | assert output == 'prompt #1 > prompt #2 > prompt #3 > prompt #4 > \n' 261 | 262 | os.remove('.history') 263 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/click-shell.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/click-shell.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/click-shell" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/click-shell" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # click-shell documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Dec 13 02:02:08 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import io 16 | import os 17 | 18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 | 20 | 21 | def read(rel_path): 22 | with io.open(os.path.join(BASE_DIR, rel_path), 'r') as fp: 23 | return fp.read() 24 | 25 | 26 | def get_version(rel_path): 27 | for line in read(rel_path).splitlines(): 28 | if line.startswith('__version__'): 29 | # __version__ = "0.9" 30 | delim = '"' if '"' in line else "'" 31 | return line.split(delim)[1] 32 | else: 33 | raise RuntimeError("Unable to find version string.") 34 | 35 | 36 | # If extensions (or modules to document with autodoc) are in another directory, 37 | # add these directories to sys.path here. If the directory is relative to the 38 | # documentation root, use os.path.abspath to make it absolute, like shown here. 39 | # sys.path.insert(0, os.path.abspath('.')) 40 | 41 | # -- General configuration ------------------------------------------------ 42 | 43 | # If your documentation needs a minimal Sphinx version, state it here. 44 | # needs_sphinx = '1.0' 45 | 46 | # Add any Sphinx extension module names here, as strings. They can be 47 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 48 | # ones. 49 | extensions = [ 50 | 'sphinx.ext.autodoc', 51 | 'sphinx.ext.doctest', 52 | 'sphinx.ext.intersphinx', 53 | 'sphinx.ext.todo', 54 | 'sphinx.ext.coverage', 55 | 'sphinx.ext.mathjax', 56 | 'sphinx.ext.ifconfig', 57 | 'sphinx.ext.viewcode', 58 | ] 59 | 60 | # Add any paths that contain templates here, relative to this directory. 61 | templates_path = ['_templates'] 62 | 63 | # The suffix(es) of source filenames. 64 | # You can specify multiple suffix as a list of string: 65 | # source_suffix = ['.rst', '.md'] 66 | source_suffix = '.rst' 67 | 68 | # The encoding of source files. 69 | # source_encoding = 'utf-8-sig' 70 | 71 | # The master toctree document. 72 | master_doc = 'index' 73 | 74 | # General information about the project. 75 | project = u'click-shell' 76 | copyright = u'2015, Clark Perkins' 77 | author = u'Clark Perkins' 78 | 79 | # The version info for the project you're documenting, acts as replacement for 80 | # |version| and |release|, also used in various other places throughout the 81 | # built documents. 82 | # 83 | # The full version, including alpha/beta/rc tags. 84 | release = get_version('click_shell/__init__.py') 85 | # The short X.Y version. 86 | version = release.split('.')[0] 87 | 88 | # The language for content autogenerated by Sphinx. Refer to documentation 89 | # for a list of supported languages. 90 | # 91 | # This is also used if you do content translation via gettext catalogs. 92 | # Usually you set "language" from the command line for these cases. 93 | language = None 94 | 95 | # There are two options for replacing |today|: either, you set today to some 96 | # non-false value, then it is used: 97 | # today = '' 98 | # Else, today_fmt is used as the format for a strftime call. 99 | # today_fmt = '%B %d, %Y' 100 | 101 | # List of patterns, relative to source directory, that match files and 102 | # directories to ignore when looking for source files. 103 | exclude_patterns = ['_build'] 104 | 105 | # The reST default role (used for this markup: `text`) to use for all 106 | # documents. 107 | # default_role = None 108 | 109 | # If true, '()' will be appended to :func: etc. cross-reference text. 110 | # add_function_parentheses = True 111 | 112 | # If true, the current module name will be prepended to all description 113 | # unit titles (such as .. function::). 114 | # add_module_names = True 115 | 116 | # If true, sectionauthor and moduleauthor directives will be shown in the 117 | # output. They are ignored by default. 118 | # show_authors = False 119 | 120 | # The name of the Pygments (syntax highlighting) style to use. 121 | pygments_style = 'sphinx' 122 | 123 | # A list of ignored prefixes for module index sorting. 124 | # modindex_common_prefix = [] 125 | 126 | # If true, keep warnings as "system message" paragraphs in the built documents. 127 | # keep_warnings = False 128 | 129 | # If true, `todo` and `todoList` produce output, else they produce nothing. 130 | todo_include_todos = True 131 | 132 | 133 | # -- Options for HTML output ---------------------------------------------- 134 | 135 | # The theme to use for HTML and HTML Help pages. See the documentation for 136 | # a list of builtin themes. 137 | html_theme = 'sphinx_rtd_theme' 138 | 139 | # Theme options are theme-specific and customize the look and feel of a theme 140 | # further. For a list of options available for each theme, see the 141 | # documentation. 142 | # html_theme_options = {} 143 | 144 | # Add any paths that contain custom themes here, relative to this directory. 145 | # html_theme_path = [] 146 | 147 | # The name for this set of Sphinx documents. If None, it defaults to 148 | # " v documentation". 149 | # html_title = None 150 | 151 | # A shorter title for the navigation bar. Default is the same as html_title. 152 | # html_short_title = None 153 | 154 | # The name of an image file (relative to this directory) to place at the top 155 | # of the sidebar. 156 | # html_logo = None 157 | 158 | # The name of an image file (within the static path) to use as favicon of the 159 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 160 | # pixels large. 161 | # html_favicon = None 162 | 163 | # Add any paths that contain custom static files (such as style sheets) here, 164 | # relative to this directory. They are copied after the builtin static files, 165 | # so a file named "default.css" will overwrite the builtin "default.css". 166 | html_static_path = ['_static'] 167 | 168 | # Add any extra paths that contain custom files (such as robots.txt or 169 | # .htaccess) here, relative to this directory. These files are copied 170 | # directly to the root of the documentation. 171 | # html_extra_path = [] 172 | 173 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 174 | # using the given strftime format. 175 | # html_last_updated_fmt = '%b %d, %Y' 176 | 177 | # If true, SmartyPants will be used to convert quotes and dashes to 178 | # typographically correct entities. 179 | # html_use_smartypants = True 180 | 181 | # Custom sidebar templates, maps document names to template names. 182 | # html_sidebars = {} 183 | 184 | # Additional templates that should be rendered to pages, maps page names to 185 | # template names. 186 | # html_additional_pages = {} 187 | 188 | # If false, no module index is generated. 189 | # html_domain_indices = True 190 | 191 | # If false, no index is generated. 192 | # html_use_index = True 193 | 194 | # If true, the index is split into individual pages for each letter. 195 | # html_split_index = False 196 | 197 | # If true, links to the reST sources are added to the pages. 198 | # html_show_sourcelink = True 199 | 200 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 201 | # html_show_sphinx = True 202 | 203 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 204 | # html_show_copyright = True 205 | 206 | # If true, an OpenSearch description file will be output, and all pages will 207 | # contain a tag referring to it. The value of this option must be the 208 | # base URL from which the finished HTML is served. 209 | # html_use_opensearch = '' 210 | 211 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 212 | # html_file_suffix = None 213 | 214 | # Language to be used for generating the HTML full-text search index. 215 | # Sphinx supports the following languages: 216 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 217 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 218 | # html_search_language = 'en' 219 | 220 | # A dictionary with options for the search language support, empty by default. 221 | # Now only 'ja' uses this config value 222 | # html_search_options = {'type': 'default'} 223 | 224 | # The name of a javascript file (relative to the configuration directory) that 225 | # implements a search results scorer. If empty, the default will be used. 226 | # html_search_scorer = 'scorer.js' 227 | 228 | # Output file base name for HTML help builder. 229 | htmlhelp_basename = 'click-shelldoc' 230 | 231 | # -- Options for LaTeX output --------------------------------------------- 232 | 233 | latex_elements = { 234 | # The paper size ('letterpaper' or 'a4paper'). 235 | # 'papersize': 'letterpaper', 236 | 237 | # The font size ('10pt', '11pt' or '12pt'). 238 | # 'pointsize': '10pt', 239 | 240 | # Additional stuff for the LaTeX preamble. 241 | # 'preamble': '', 242 | 243 | # Latex figure (float) alignment 244 | # 'figure_align': 'htbp', 245 | } 246 | 247 | # Grouping the document tree into LaTeX files. List of tuples 248 | # (source start file, target name, title, 249 | # author, documentclass [howto, manual, or own class]). 250 | latex_documents = [ 251 | (master_doc, 'click-shell.tex', u'click-shell Documentation', 252 | u'Clark Perkins', 'manual'), 253 | ] 254 | 255 | # The name of an image file (relative to this directory) to place at the top of 256 | # the title page. 257 | # latex_logo = None 258 | 259 | # For "manual" documents, if this is true, then toplevel headings are parts, 260 | # not chapters. 261 | # latex_use_parts = False 262 | 263 | # If true, show page references after internal links. 264 | # latex_show_pagerefs = False 265 | 266 | # If true, show URL addresses after external links. 267 | # latex_show_urls = False 268 | 269 | # Documents to append as an appendix to all manuals. 270 | # latex_appendices = [] 271 | 272 | # If false, no module index is generated. 273 | # latex_domain_indices = True 274 | 275 | 276 | # -- Options for manual page output --------------------------------------- 277 | 278 | # One entry per manual page. List of tuples 279 | # (source start file, name, description, authors, manual section). 280 | man_pages = [ 281 | (master_doc, 'click-shell', u'click-shell Documentation', 282 | [author], 1) 283 | ] 284 | 285 | # If true, show URL addresses after external links. 286 | # man_show_urls = False 287 | 288 | 289 | # -- Options for Texinfo output ------------------------------------------- 290 | 291 | # Grouping the document tree into Texinfo files. List of tuples 292 | # (source start file, target name, title, author, 293 | # dir menu entry, description, category) 294 | texinfo_documents = [ 295 | (master_doc, 'click-shell', u'click-shell Documentation', 296 | author, 'click-shell', 'One line description of project.', 297 | 'Miscellaneous'), 298 | ] 299 | 300 | # Documents to append as an appendix to all manuals. 301 | # texinfo_appendices = [] 302 | 303 | # If false, no module index is generated. 304 | # texinfo_domain_indices = True 305 | 306 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 307 | # texinfo_show_urls = 'footnote' 308 | 309 | # If true, do not generate a @detailmenu in the "Top" node's menu. 310 | # texinfo_no_detailmenu = False 311 | 312 | 313 | # -- Options for Epub output ---------------------------------------------- 314 | 315 | # Bibliographic Dublin Core info. 316 | epub_title = project 317 | epub_author = author 318 | epub_publisher = author 319 | epub_copyright = copyright 320 | 321 | # The basename for the epub file. It defaults to the project name. 322 | # epub_basename = project 323 | 324 | # The HTML theme for the epub output. Since the default themes are not optimized 325 | # for small screen space, using the same theme for HTML and epub output is 326 | # usually not wise. This defaults to 'epub', a theme designed to save visual 327 | # space. 328 | # epub_theme = 'epub' 329 | 330 | # The language of the text. It defaults to the language option 331 | # or 'en' if the language is not set. 332 | # epub_language = '' 333 | 334 | # The scheme of the identifier. Typical schemes are ISBN or URL. 335 | # epub_scheme = '' 336 | 337 | # The unique identifier of the text. This can be a ISBN number 338 | # or the project homepage. 339 | # epub_identifier = '' 340 | 341 | # A unique identification for the text. 342 | # epub_uid = '' 343 | 344 | # A tuple containing the cover image and cover page html template filenames. 345 | # epub_cover = () 346 | 347 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 348 | # epub_guide = () 349 | 350 | # HTML files that should be inserted before the pages created by sphinx. 351 | # The format is a list of tuples containing the path and title. 352 | # epub_pre_files = [] 353 | 354 | # HTML files shat should be inserted after the pages created by sphinx. 355 | # The format is a list of tuples containing the path and title. 356 | # epub_post_files = [] 357 | 358 | # A list of files that should not be packed into the epub file. 359 | epub_exclude_files = ['search.html'] 360 | 361 | # The depth of the table of contents in toc.ncx. 362 | # epub_tocdepth = 3 363 | 364 | # Allow duplicate toc entries. 365 | # epub_tocdup = True 366 | 367 | # Choose between 'default' and 'includehidden'. 368 | # epub_tocscope = 'default' 369 | 370 | # Fix unsupported image types using the Pillow. 371 | # epub_fix_images = False 372 | 373 | # Scale large images. 374 | # epub_max_image_width = 0 375 | 376 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 377 | # epub_show_urls = 'inline' 378 | 379 | # If false, no index is generated. 380 | # epub_use_index = True 381 | 382 | 383 | # Example configuration for intersphinx: refer to the Python standard library. 384 | intersphinx_mapping = {'https://docs.python.org/': None} 385 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "0536f54d891cc05878ff66ece420f6d1dcffcb3a01180b18cad1bc58d200d628" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "click": { 18 | "hashes": [ 19 | "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", 20 | "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" 21 | ], 22 | "markers": "python_version >= '3.6'", 23 | "version": "==8.0.1" 24 | }, 25 | "click-shell": { 26 | "editable": true, 27 | "path": "." 28 | } 29 | }, 30 | "develop": { 31 | "astroid": { 32 | "hashes": [ 33 | "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e", 34 | "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975" 35 | ], 36 | "markers": "python_version ~= '3.6'", 37 | "version": "==2.5.6" 38 | }, 39 | "attrs": { 40 | "hashes": [ 41 | "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", 42 | "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" 43 | ], 44 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 45 | "version": "==21.2.0" 46 | }, 47 | "click": { 48 | "hashes": [ 49 | "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", 50 | "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" 51 | ], 52 | "markers": "python_version >= '3.6'", 53 | "version": "==8.0.1" 54 | }, 55 | "coverage": { 56 | "hashes": [ 57 | "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", 58 | "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", 59 | "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", 60 | "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", 61 | "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", 62 | "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", 63 | "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", 64 | "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", 65 | "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", 66 | "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", 67 | "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", 68 | "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", 69 | "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", 70 | "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", 71 | "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", 72 | "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", 73 | "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", 74 | "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", 75 | "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", 76 | "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", 77 | "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", 78 | "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", 79 | "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", 80 | "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", 81 | "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", 82 | "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", 83 | "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", 84 | "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", 85 | "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", 86 | "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", 87 | "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", 88 | "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", 89 | "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", 90 | "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", 91 | "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", 92 | "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", 93 | "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", 94 | "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", 95 | "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", 96 | "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", 97 | "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", 98 | "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", 99 | "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", 100 | "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", 101 | "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", 102 | "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", 103 | "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", 104 | "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", 105 | "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", 106 | "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", 107 | "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", 108 | "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" 109 | ], 110 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 111 | "version": "==5.5" 112 | }, 113 | "iniconfig": { 114 | "hashes": [ 115 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 116 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 117 | ], 118 | "version": "==1.1.1" 119 | }, 120 | "isort": { 121 | "hashes": [ 122 | "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", 123 | "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" 124 | ], 125 | "markers": "python_full_version >= '3.6.1' and python_version < '4'", 126 | "version": "==5.9.1" 127 | }, 128 | "lazy-object-proxy": { 129 | "hashes": [ 130 | "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653", 131 | "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61", 132 | "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2", 133 | "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837", 134 | "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3", 135 | "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43", 136 | "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726", 137 | "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3", 138 | "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587", 139 | "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8", 140 | "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a", 141 | "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd", 142 | "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f", 143 | "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad", 144 | "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4", 145 | "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b", 146 | "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf", 147 | "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981", 148 | "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741", 149 | "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e", 150 | "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", 151 | "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" 152 | ], 153 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 154 | "version": "==1.6.0" 155 | }, 156 | "mccabe": { 157 | "hashes": [ 158 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 159 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 160 | ], 161 | "version": "==0.6.1" 162 | }, 163 | "mypy": { 164 | "hashes": [ 165 | "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9", 166 | "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a", 167 | "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9", 168 | "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e", 169 | "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2", 170 | "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212", 171 | "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b", 172 | "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885", 173 | "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150", 174 | "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703", 175 | "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072", 176 | "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457", 177 | "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e", 178 | "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0", 179 | "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb", 180 | "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97", 181 | "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8", 182 | "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811", 183 | "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6", 184 | "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de", 185 | "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504", 186 | "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921", 187 | "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d" 188 | ], 189 | "index": "pypi", 190 | "version": "==0.910" 191 | }, 192 | "mypy-extensions": { 193 | "hashes": [ 194 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 195 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 196 | ], 197 | "version": "==0.4.3" 198 | }, 199 | "packaging": { 200 | "hashes": [ 201 | "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", 202 | "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" 203 | ], 204 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 205 | "version": "==20.9" 206 | }, 207 | "pluggy": { 208 | "hashes": [ 209 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 210 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 211 | ], 212 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 213 | "version": "==0.13.1" 214 | }, 215 | "py": { 216 | "hashes": [ 217 | "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", 218 | "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" 219 | ], 220 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 221 | "version": "==1.10.0" 222 | }, 223 | "pycodestyle": { 224 | "hashes": [ 225 | "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", 226 | "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" 227 | ], 228 | "index": "pypi", 229 | "version": "==2.7.0" 230 | }, 231 | "pylint": { 232 | "hashes": [ 233 | "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8", 234 | "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484" 235 | ], 236 | "index": "pypi", 237 | "version": "==2.8.3" 238 | }, 239 | "pyparsing": { 240 | "hashes": [ 241 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 242 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 243 | ], 244 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", 245 | "version": "==2.4.7" 246 | }, 247 | "pytest": { 248 | "hashes": [ 249 | "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", 250 | "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" 251 | ], 252 | "index": "pypi", 253 | "version": "==6.2.4" 254 | }, 255 | "pytest-click": { 256 | "hashes": [ 257 | "sha256:ac298e15a31d8892d9d063c3be82bb9877aaa11a4944e05e541ecc753258e474", 258 | "sha256:b40b8435adde7a0d931352036a3882b13cd424fce2ffd7be6c87665050259be5" 259 | ], 260 | "index": "pypi", 261 | "version": "==1.0.2" 262 | }, 263 | "pytest-cov": { 264 | "hashes": [ 265 | "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", 266 | "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7" 267 | ], 268 | "index": "pypi", 269 | "version": "==2.12.1" 270 | }, 271 | "toml": { 272 | "hashes": [ 273 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 274 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 275 | ], 276 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", 277 | "version": "==0.10.2" 278 | }, 279 | "typing-extensions": { 280 | "hashes": [ 281 | "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", 282 | "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", 283 | "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" 284 | ], 285 | "version": "==3.10.0.0" 286 | }, 287 | "wrapt": { 288 | "hashes": [ 289 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 290 | ], 291 | "version": "==1.12.1" 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS,migrations 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 21 | # number of processors available to use. 22 | jobs=0 23 | 24 | # Control the amount of potential inferred values when inferring a single 25 | # object. This can help the performance when dealing with large functions or 26 | # complex, nested conditions. 27 | limit-inference-results=100 28 | 29 | # List of plugins (as comma separated values of python modules names) to load, 30 | # usually to register additional checkers. 31 | #load-plugins= 32 | 33 | # Pickle collected data for later comparisons. 34 | persistent=yes 35 | 36 | # Specify a configuration file. 37 | #rcfile= 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | locally-enabled, 77 | file-ignored, 78 | suppressed-message, 79 | useless-suppression, 80 | deprecated-pragma, 81 | use-symbolic-message-instead, 82 | apply-builtin, 83 | basestring-builtin, 84 | buffer-builtin, 85 | cmp-builtin, 86 | coerce-builtin, 87 | execfile-builtin, 88 | file-builtin, 89 | long-builtin, 90 | raw_input-builtin, 91 | reduce-builtin, 92 | standarderror-builtin, 93 | unicode-builtin, 94 | xrange-builtin, 95 | coerce-method, 96 | delslice-method, 97 | getslice-method, 98 | setslice-method, 99 | no-absolute-import, 100 | old-division, 101 | dict-iter-method, 102 | dict-view-method, 103 | next-method-called, 104 | metaclass-assignment, 105 | indexing-exception, 106 | raising-string, 107 | reload-builtin, 108 | oct-method, 109 | hex-method, 110 | nonzero-method, 111 | cmp-method, 112 | input-builtin, 113 | round-builtin, 114 | intern-builtin, 115 | unichr-builtin, 116 | map-builtin-not-iterating, 117 | zip-builtin-not-iterating, 118 | range-builtin-not-iterating, 119 | filter-builtin-not-iterating, 120 | using-cmp-argument, 121 | eq-without-hash, 122 | div-method, 123 | idiv-method, 124 | rdiv-method, 125 | exception-message-attribute, 126 | invalid-str-codec, 127 | sys-max-int, 128 | bad-python3-import, 129 | deprecated-string-function, 130 | deprecated-str-translate-call, 131 | deprecated-itertools-function, 132 | deprecated-types-field, 133 | next-method-defined, 134 | dict-items-not-iterating, 135 | dict-keys-not-iterating, 136 | dict-values-not-iterating, 137 | deprecated-operator-function, 138 | deprecated-urllib-function, 139 | xreadlines-attribute, 140 | deprecated-sys-function, 141 | exception-escape, 142 | comprehension-escape, 143 | 144 | # Extras 145 | invalid-name, 146 | logging-fstring-interpolation, 147 | missing-docstring, 148 | no-else-return, 149 | too-few-public-methods 150 | 151 | # Enable the message, report, category or checker with the given id(s). You can 152 | # either give multiple identifier separated by comma (,) or put this option 153 | # multiple time (only on the command line, not in the configuration file where 154 | # it should appear only once). See also the "--disable" option for examples. 155 | enable=c-extension-no-member 156 | 157 | 158 | [REPORTS] 159 | 160 | # Python expression which should return a note less than 10 (10 is the highest 161 | # note). You have access to the variables errors warning, statement which 162 | # respectively contain the number of errors / warnings messages and the total 163 | # number of statements analyzed. This is used by the global evaluation report 164 | # (RP0004). 165 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 166 | 167 | # Template used to display messages. This is a python new-style format string 168 | # used to format the message information. See doc for all details. 169 | #msg-template= 170 | 171 | # Set the output format. Available formats are text, parseable, colorized, json 172 | # and msvs (visual studio). You can also give a reporter class, e.g. 173 | # mypackage.mymodule.MyReporterClass. 174 | output-format=text 175 | 176 | # Tells whether to display a full report or only the messages. 177 | reports=no 178 | 179 | # Activate the evaluation score. 180 | score=yes 181 | 182 | 183 | [REFACTORING] 184 | 185 | # Maximum number of nested blocks for function / method body 186 | max-nested-blocks=5 187 | 188 | # Complete name of functions that never returns. When checking for 189 | # inconsistent-return-statements if a never returning function is called then 190 | # it will be considered as an explicit return statement and no message will be 191 | # printed. 192 | never-returning-functions=sys.exit 193 | 194 | 195 | [LOGGING] 196 | 197 | # Logging modules to check that the string format arguments are in logging 198 | # function parameter format. 199 | logging-modules=logging 200 | 201 | 202 | [SPELLING] 203 | 204 | # Limits count of emitted suggestions for spelling mistakes. 205 | max-spelling-suggestions=4 206 | 207 | # Spelling dictionary name. Available dictionaries: none. To make it working 208 | # install python-enchant package.. 209 | spelling-dict= 210 | 211 | # List of comma separated words that should not be checked. 212 | spelling-ignore-words= 213 | 214 | # A path to a file that contains private dictionary; one word per line. 215 | spelling-private-dict-file= 216 | 217 | # Tells whether to store unknown words to indicated private dictionary in 218 | # --spelling-private-dict-file option instead of raising a message. 219 | spelling-store-unknown-words=no 220 | 221 | 222 | [MISCELLANEOUS] 223 | 224 | # List of note tags to take in consideration, separated by a comma. 225 | notes=FIXME, 226 | XXX, 227 | TODO 228 | 229 | 230 | [TYPECHECK] 231 | 232 | # List of decorators that produce context managers, such as 233 | # contextlib.contextmanager. Add to this list to register other decorators that 234 | # produce valid context managers. 235 | contextmanager-decorators=contextlib.contextmanager 236 | 237 | # List of members which are set dynamically and missed by pylint inference 238 | # system, and so shouldn't trigger E1101 when accessed. Python regular 239 | # expressions are accepted. 240 | generated-members= 241 | 242 | # Tells whether missing members accessed in mixin class should be ignored. A 243 | # mixin class is detected if its name ends with "mixin" (case insensitive). 244 | ignore-mixin-members=yes 245 | 246 | # Tells whether to warn about missing members when the owner of the attribute 247 | # is inferred to be None. 248 | ignore-none=yes 249 | 250 | # This flag controls whether pylint should warn about no-member and similar 251 | # checks whenever an opaque object is returned when inferring. The inference 252 | # can return multiple potential results while evaluating a Python object, but 253 | # some branches might not be evaluated, which results in partial inference. In 254 | # that case, it might be useful to still emit no-member and other checks for 255 | # the rest of the inferred objects. 256 | ignore-on-opaque-inference=yes 257 | 258 | # List of class names for which member attributes should not be checked (useful 259 | # for classes with dynamically set attributes). This supports the use of 260 | # qualified names. 261 | ignored-classes=optparse.Values,thread._local,_thread._local 262 | 263 | # List of module names for which member attributes should not be checked 264 | # (useful for modules/projects where namespaces are manipulated during runtime 265 | # and thus existing member attributes cannot be deduced by static analysis. It 266 | # supports qualified module names, as well as Unix pattern matching. 267 | ignored-modules= 268 | 269 | # Show a hint with possible names when a member name was not found. The aspect 270 | # of finding the hint is based on edit distance. 271 | missing-member-hint=yes 272 | 273 | # The minimum edit distance a name should have in order to be considered a 274 | # similar match for a missing member name. 275 | missing-member-hint-distance=1 276 | 277 | # The total number of similar names that should be taken in consideration when 278 | # showing a hint for a missing member. 279 | missing-member-max-choices=1 280 | 281 | 282 | [VARIABLES] 283 | 284 | # List of additional names supposed to be defined in builtins. Remember that 285 | # you should avoid to define new builtins when possible. 286 | additional-builtins= 287 | 288 | # Tells whether unused global variables should be treated as a violation. 289 | allow-global-unused-variables=yes 290 | 291 | # List of strings which can identify a callback function by name. A callback 292 | # name must start or end with one of those strings. 293 | callbacks=cb_, 294 | _cb 295 | 296 | # A regular expression matching the name of dummy variables (i.e. expected to 297 | # not be used). 298 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 299 | 300 | # Argument names that match this expression will be ignored. Default to name 301 | # with leading underscore. 302 | ignored-argument-names=_.*|^ignored_|^unused_ 303 | 304 | # Tells whether we should check for unused import in __init__ files. 305 | init-import=no 306 | 307 | # List of qualified module names which can have objects that can redefine 308 | # builtins. 309 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 310 | 311 | 312 | [FORMAT] 313 | 314 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 315 | expected-line-ending-format= 316 | 317 | # Regexp for a line that is allowed to be longer than the limit. 318 | ignore-long-lines=^\s*(# )??$ 319 | 320 | # Number of spaces of indent required inside a hanging or continued line. 321 | indent-after-paren=4 322 | 323 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 324 | # tab). 325 | indent-string=' ' 326 | 327 | # Maximum number of characters on a single line. 328 | max-line-length=100 329 | 330 | # Maximum number of lines in a module. 331 | max-module-lines=1000 332 | 333 | # List of optional constructs for which whitespace checking is disabled. `dict- 334 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 335 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 336 | # `empty-line` allows space-only lines. 337 | no-space-check=trailing-comma, 338 | dict-separator 339 | 340 | # Allow the body of a class to be on the same line as the declaration if body 341 | # contains single statement. 342 | single-line-class-stmt=no 343 | 344 | # Allow the body of an if to be on the same line as the test if there is no 345 | # else. 346 | single-line-if-stmt=no 347 | 348 | 349 | [SIMILARITIES] 350 | 351 | # Ignore comments when computing similarities. 352 | ignore-comments=yes 353 | 354 | # Ignore docstrings when computing similarities. 355 | ignore-docstrings=yes 356 | 357 | # Ignore imports when computing similarities. 358 | ignore-imports=no 359 | 360 | # Minimum lines number of a similarity. 361 | min-similarity-lines=4 362 | 363 | 364 | [BASIC] 365 | 366 | # Naming style matching correct argument names. 367 | argument-naming-style=snake_case 368 | 369 | # Regular expression matching correct argument names. Overrides argument- 370 | # naming-style. 371 | #argument-rgx= 372 | 373 | # Naming style matching correct attribute names. 374 | attr-naming-style=snake_case 375 | 376 | # Regular expression matching correct attribute names. Overrides attr-naming- 377 | # style. 378 | #attr-rgx= 379 | 380 | # Bad variable names which should always be refused, separated by a comma. 381 | bad-names=foo, 382 | bar, 383 | baz, 384 | toto, 385 | tutu, 386 | tata 387 | 388 | # Naming style matching correct class attribute names. 389 | class-attribute-naming-style=any 390 | 391 | # Regular expression matching correct class attribute names. Overrides class- 392 | # attribute-naming-style. 393 | #class-attribute-rgx= 394 | 395 | # Naming style matching correct class names. 396 | class-naming-style=PascalCase 397 | 398 | # Regular expression matching correct class names. Overrides class-naming- 399 | # style. 400 | #class-rgx= 401 | 402 | # Naming style matching correct constant names. 403 | const-naming-style=UPPER_CASE 404 | 405 | # Regular expression matching correct constant names. Overrides const-naming- 406 | # style. 407 | #const-rgx= 408 | 409 | # Minimum line length for functions/classes that require docstrings, shorter 410 | # ones are exempt. 411 | docstring-min-length=-1 412 | 413 | # Naming style matching correct function names. 414 | function-naming-style=snake_case 415 | 416 | # Regular expression matching correct function names. Overrides function- 417 | # naming-style. 418 | #function-rgx= 419 | 420 | # Good variable names which should always be accepted, separated by a comma. 421 | good-names=i, 422 | j, 423 | k, 424 | ex, 425 | Run, 426 | _ 427 | 428 | # Include a hint for the correct naming format with invalid-name. 429 | include-naming-hint=no 430 | 431 | # Naming style matching correct inline iteration names. 432 | inlinevar-naming-style=any 433 | 434 | # Regular expression matching correct inline iteration names. Overrides 435 | # inlinevar-naming-style. 436 | #inlinevar-rgx= 437 | 438 | # Naming style matching correct method names. 439 | method-naming-style=snake_case 440 | 441 | # Regular expression matching correct method names. Overrides method-naming- 442 | # style. 443 | #method-rgx= 444 | 445 | # Naming style matching correct module names. 446 | module-naming-style=snake_case 447 | 448 | # Regular expression matching correct module names. Overrides module-naming- 449 | # style. 450 | #module-rgx= 451 | 452 | # Colon-delimited sets of names that determine each other's naming style when 453 | # the name regexes allow several styles. 454 | name-group= 455 | 456 | # Regular expression which should only match function or class names that do 457 | # not require a docstring. 458 | no-docstring-rgx=^_ 459 | 460 | # List of decorators that produce properties, such as abc.abstractproperty. Add 461 | # to this list to register other decorators that produce valid properties. 462 | property-classes=abc.abstractproperty 463 | 464 | # Naming style matching correct variable names. 465 | variable-naming-style=snake_case 466 | 467 | # Regular expression matching correct variable names. Overrides variable- 468 | # naming-style. 469 | #variable-rgx= 470 | 471 | 472 | [IMPORTS] 473 | 474 | # Allow wildcard imports from modules that define __all__. 475 | allow-wildcard-with-all=no 476 | 477 | # Analyse import fallback blocks. This can be used to support both Python 2 and 478 | # 3 compatible code, which means that the block might have code that exists 479 | # only in one or another interpreter, leading to false positives when analysed. 480 | analyse-fallback-blocks=no 481 | 482 | # Deprecated modules which should not be used, separated by a comma. 483 | deprecated-modules=optparse,tkinter.tix 484 | 485 | # Create a graph of external dependencies in the given file (report RP0402 must 486 | # not be disabled). 487 | ext-import-graph= 488 | 489 | # Create a graph of every (i.e. internal and external) dependencies in the 490 | # given file (report RP0402 must not be disabled). 491 | import-graph= 492 | 493 | # Create a graph of internal dependencies in the given file (report RP0402 must 494 | # not be disabled). 495 | int-import-graph= 496 | 497 | # Force import order to recognize a module as part of the standard 498 | # compatibility libraries. 499 | known-standard-library= 500 | 501 | # Force import order to recognize a module as part of a third party library. 502 | known-third-party=enchant 503 | 504 | 505 | [CLASSES] 506 | 507 | # List of method names used to declare (i.e. assign) instance attributes. 508 | defining-attr-methods=__init__, 509 | __new__, 510 | setUp 511 | 512 | # List of member names, which should be excluded from the protected access 513 | # warning. 514 | exclude-protected=_asdict, 515 | _fields, 516 | _replace, 517 | _source, 518 | _make 519 | 520 | # List of valid names for the first argument in a class method. 521 | valid-classmethod-first-arg=cls 522 | 523 | # List of valid names for the first argument in a metaclass class method. 524 | valid-metaclass-classmethod-first-arg=cls 525 | 526 | 527 | [DESIGN] 528 | 529 | # Maximum number of arguments for function / method. 530 | max-args=5 531 | 532 | # Maximum number of attributes for a class (see R0902). 533 | max-attributes=15 534 | 535 | # Maximum number of boolean expressions in an if statement. 536 | max-bool-expr=5 537 | 538 | # Maximum number of branch for function / method body. 539 | max-branches=12 540 | 541 | # Maximum number of locals for function / method body. 542 | max-locals=15 543 | 544 | # Maximum number of parents for a class (see R0901). 545 | max-parents=15 546 | 547 | # Maximum number of public methods for a class (see R0904). 548 | max-public-methods=20 549 | 550 | # Maximum number of return / yield for function / method body. 551 | max-returns=6 552 | 553 | # Maximum number of statements in function / method body. 554 | max-statements=50 555 | 556 | # Minimum number of public methods for a class (see R0903). 557 | min-public-methods=2 558 | 559 | 560 | [EXCEPTIONS] 561 | 562 | # Exceptions that will emit a warning when being caught. Defaults to 563 | # "Exception". 564 | overgeneral-exceptions=Exception 565 | --------------------------------------------------------------------------------