Update upstream source from tag 'upstream/8.1.3'
Update to upstream version '8.1.3'
with Debian dir 98980b6e7f
This commit is contained in:
commit
987cdb48cc
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
|
@ -1,8 +1,9 @@
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: pip
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: monthly
|
interval: "monthly"
|
||||||
time: "08:00"
|
day: "monday"
|
||||||
open-pull-requests-limit: 99
|
time: "16:00"
|
||||||
|
timezone: "UTC"
|
||||||
|
|
6
.github/workflows/lock.yaml
vendored
6
.github/workflows/lock.yaml
vendored
|
@ -8,8 +8,8 @@ jobs:
|
||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v2
|
- uses: dessant/lock-threads@v3
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-lock-inactive-days: 14
|
issue-inactive-days: 14
|
||||||
pr-lock-inactive-days: 14
|
pr-inactive-days: 14
|
||||||
|
|
25
.github/workflows/tests.yaml
vendored
25
.github/workflows/tests.yaml
vendored
|
@ -24,32 +24,27 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- {name: Linux, python: '3.9', os: ubuntu-latest, tox: py39}
|
- {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310}
|
||||||
- {name: Windows, python: '3.9', os: windows-latest, tox: py39}
|
- {name: Windows, python: '3.10', os: windows-latest, tox: py310}
|
||||||
- {name: Mac, python: '3.9', os: macos-latest, tox: py39}
|
- {name: Mac, python: '3.10', os: macos-latest, tox: py310}
|
||||||
|
- {name: '3.11-dev', python: '3.11-dev', os: ubuntu-latest, tox: py311}
|
||||||
|
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
|
||||||
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
|
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
|
||||||
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
|
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
|
||||||
- {name: '3.6', python: '3.6', os: ubuntu-latest, tox: py36}
|
- {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37}
|
||||||
- {name: 'PyPy', python: pypy3, os: ubuntu-latest, tox: pypy3}
|
- {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing}
|
||||||
- {name: Typing, python: '3.9', os: ubuntu-latest, tox: typing}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python }}
|
python-version: ${{ matrix.python }}
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: 'requirements/*.txt'
|
||||||
- name: update pip
|
- name: update pip
|
||||||
run: |
|
run: |
|
||||||
pip install -U wheel
|
pip install -U wheel
|
||||||
pip install -U setuptools
|
pip install -U setuptools
|
||||||
python -m pip install -U pip
|
python -m pip install -U pip
|
||||||
- name: get pip cache dir
|
|
||||||
id: pip-cache
|
|
||||||
run: echo "::set-output name=dir::$(pip cache dir)"
|
|
||||||
- name: cache pip
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
|
||||||
key: pip|${{ runner.os }}|${{ matrix.python }}|${{ hashFiles('setup.py') }}|${{ hashFiles('requirements/*.txt') }}
|
|
||||||
- name: cache mypy
|
- name: cache mypy
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
/.idea/
|
/.idea/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
|
.DS_Store/
|
||||||
/env/
|
/env/
|
||||||
/venv/
|
/venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
ci:
|
ci:
|
||||||
|
autoupdate_branch: "8.1.x"
|
||||||
autoupdate_schedule: monthly
|
autoupdate_schedule: monthly
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.29.0
|
rev: v2.32.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--py36-plus"]
|
args: ["--py37-plus"]
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v2.6.0
|
rev: v3.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: ["--application-directories", "src"]
|
args: ["--application-directories", "src"]
|
||||||
|
additional_dependencies: ["setuptools>60.9"]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.9b0
|
rev: 22.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 3.9.2
|
rev: 4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- flake8-bugbear
|
- flake8-bugbear
|
||||||
- flake8-implicit-str-concat
|
- flake8-implicit-str-concat
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.0.1
|
rev: v4.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: fix-byte-order-marker
|
- id: fix-byte-order-marker
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
version: 2
|
version: 2
|
||||||
|
build:
|
||||||
|
os: ubuntu-20.04
|
||||||
|
tools:
|
||||||
|
python: "3.10"
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
- requirements: requirements/docs.txt
|
- requirements: requirements/docs.txt
|
||||||
|
|
110
CHANGES.rst
110
CHANGES.rst
|
@ -1,5 +1,115 @@
|
||||||
.. currentmodule:: click
|
.. currentmodule:: click
|
||||||
|
|
||||||
|
Version 8.1.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2022-04-28
|
||||||
|
|
||||||
|
- Use verbose form of ``typing.Callable`` for ``@command`` and
|
||||||
|
``@group``. :issue:`2255`
|
||||||
|
- Show error when attempting to create an option with
|
||||||
|
``multiple=True, is_flag=True``. Use ``count`` instead.
|
||||||
|
:issue:`2246`
|
||||||
|
|
||||||
|
|
||||||
|
Version 8.1.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2022-03-31
|
||||||
|
|
||||||
|
- Fix error message for readable path check that was mixed up with the
|
||||||
|
executable check. :pr:`2236`
|
||||||
|
- Restore parameter order for ``Path``, placing the ``executable``
|
||||||
|
parameter at the end. It is recommended to use keyword arguments
|
||||||
|
instead of positional arguments. :issue:`2235`
|
||||||
|
|
||||||
|
|
||||||
|
Version 8.1.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2022-03-30
|
||||||
|
|
||||||
|
- Fix an issue with decorator typing that caused type checking to
|
||||||
|
report that a command was not callable. :issue:`2227`
|
||||||
|
|
||||||
|
|
||||||
|
Version 8.1.0
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2022-03-28
|
||||||
|
|
||||||
|
- Drop support for Python 3.6. :pr:`2129`
|
||||||
|
- Remove previously deprecated code. :pr:`2130`
|
||||||
|
|
||||||
|
- ``Group.resultcallback`` is renamed to ``result_callback``.
|
||||||
|
- ``autocompletion`` parameter to ``Command`` is renamed to
|
||||||
|
``shell_complete``.
|
||||||
|
- ``get_terminal_size`` is removed, use
|
||||||
|
``shutil.get_terminal_size`` instead.
|
||||||
|
- ``get_os_args`` is removed, use ``sys.argv[1:]`` instead.
|
||||||
|
|
||||||
|
- Rely on :pep:`538` and :pep:`540` to handle selecting UTF-8 encoding
|
||||||
|
instead of ASCII. Click's locale encoding detection is removed.
|
||||||
|
:issue:`2198`
|
||||||
|
- Single options boolean flags with ``show_default=True`` only show
|
||||||
|
the default if it is ``True``. :issue:`1971`
|
||||||
|
- The ``command`` and ``group`` decorators can be applied with or
|
||||||
|
without parentheses. :issue:`1359`
|
||||||
|
- The ``Path`` type can check whether the target is executable.
|
||||||
|
:issue:`1961`
|
||||||
|
- ``Command.show_default`` overrides ``Context.show_default``, instead
|
||||||
|
of the other way around. :issue:`1963`
|
||||||
|
- Parameter decorators and ``@group`` handles ``cls=None`` the same as
|
||||||
|
not passing ``cls``. ``@option`` handles ``help=None`` the same as
|
||||||
|
not passing ``help``. :issue:`#1959`
|
||||||
|
- A flag option with ``required=True`` requires that the flag is
|
||||||
|
passed instead of choosing the implicit default value. :issue:`1978`
|
||||||
|
- Indentation in help text passed to ``Option`` and ``Command`` is
|
||||||
|
cleaned the same as using the ``@option`` and ``@command``
|
||||||
|
decorators does. A command's ``epilog`` and ``short_help`` are also
|
||||||
|
processed. :issue:`1985`
|
||||||
|
- Store unprocessed ``Command.help``, ``epilog`` and ``short_help``
|
||||||
|
strings. Processing is only done when formatting help text for
|
||||||
|
output. :issue:`2149`
|
||||||
|
- Allow empty str input for ``prompt()`` when
|
||||||
|
``confirmation_prompt=True`` and ``default=""``. :issue:`2157`
|
||||||
|
- Windows glob pattern expansion doesn't fail if a value is an invalid
|
||||||
|
pattern. :issue:`2195`
|
||||||
|
- It's possible to pass a list of ``params`` to ``@command``. Any
|
||||||
|
params defined with decorators are appended to the passed params.
|
||||||
|
:issue:`2131`.
|
||||||
|
- ``@command`` decorator is annotated as returning the correct type if
|
||||||
|
a ``cls`` argument is used. :issue:`2211`
|
||||||
|
- A ``Group`` with ``invoke_without_command=True`` and ``chain=False``
|
||||||
|
will invoke its result callback with the group function's return
|
||||||
|
value. :issue:`2124`
|
||||||
|
- ``to_info_dict`` will not fail if a ``ParamType`` doesn't define a
|
||||||
|
``name``. :issue:`2168`
|
||||||
|
- Shell completion prioritizes option values with option prefixes over
|
||||||
|
new options. :issue:`2040`
|
||||||
|
- Options that get an environment variable value using
|
||||||
|
``autoenvvar_prefix`` treat an empty value as ``None``, consistent
|
||||||
|
with a direct ``envvar``. :issue:`2146`
|
||||||
|
|
||||||
|
|
||||||
|
Version 8.0.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2022-02-18
|
||||||
|
|
||||||
|
- ``open_file`` recognizes ``Path("-")`` as a standard stream, the
|
||||||
|
same as the string ``"-"``. :issue:`2106`
|
||||||
|
- The ``option`` and ``argument`` decorators preserve the type
|
||||||
|
annotation of the decorated function. :pr:`2155`
|
||||||
|
- A callable default value can customize its help text by overriding
|
||||||
|
``__str__`` instead of always showing ``(dynamic)``. :issue:`2099`
|
||||||
|
- Fix a typo in the Bash completion script that affected file and
|
||||||
|
directory completion. If this script was generated by a previous
|
||||||
|
version, it should be regenerated. :issue:`2163`
|
||||||
|
- Fix typing for ``echo`` and ``secho`` file argument.
|
||||||
|
:issue:`2174, 2185`
|
||||||
|
|
||||||
|
|
||||||
Version 8.0.3
|
Version 8.0.3
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
BIN
docs/.DS_Store
vendored
BIN
docs/.DS_Store
vendored
Binary file not shown.
|
@ -63,8 +63,6 @@ Utilities
|
||||||
|
|
||||||
.. autofunction:: pause
|
.. autofunction:: pause
|
||||||
|
|
||||||
.. autofunction:: get_terminal_size
|
|
||||||
|
|
||||||
.. autofunction:: get_binary_stream
|
.. autofunction:: get_binary_stream
|
||||||
|
|
||||||
.. autofunction:: get_text_stream
|
.. autofunction:: get_text_stream
|
||||||
|
|
|
@ -109,6 +109,27 @@ To show the default values when showing command help, use ``show_default=True``
|
||||||
|
|
||||||
invoke(dots, args=['--help'])
|
invoke(dots, args=['--help'])
|
||||||
|
|
||||||
|
For single option boolean flags, the default remains hidden if the default
|
||||||
|
value is False.
|
||||||
|
|
||||||
|
.. click:example::
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--n', default=1, show_default=True)
|
||||||
|
@click.option("--gr", is_flag=True, show_default=True, default=False, help="Greet the world.")
|
||||||
|
@click.option("--br", is_flag=True, show_default=True, default=True, help="Add a thematic break")
|
||||||
|
def dots(n, gr, br):
|
||||||
|
if gr:
|
||||||
|
click.echo('Hello world!')
|
||||||
|
click.echo('.' * n)
|
||||||
|
if br:
|
||||||
|
click.echo('-' * n)
|
||||||
|
|
||||||
|
.. click:run::
|
||||||
|
|
||||||
|
invoke(dots, args=['--help'])
|
||||||
|
|
||||||
|
|
||||||
Multi Value Options
|
Multi Value Options
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -538,10 +559,10 @@ parameter ``--foo`` was required and defined before, you would need to
|
||||||
specify it for ``--version`` to work. For more information, see
|
specify it for ``--version`` to work. For more information, see
|
||||||
:ref:`callback-evaluation-order`.
|
:ref:`callback-evaluation-order`.
|
||||||
|
|
||||||
A callback is a function that is invoked with two parameters: the current
|
A callback is a function that is invoked with three parameters: the
|
||||||
:class:`Context` and the value. The context provides some useful features
|
current :class:`Context`, the current :class:`Parameter`, and the value.
|
||||||
such as quitting the application and gives access to other already
|
The context provides some useful features such as quitting the
|
||||||
processed parameters.
|
application and gives access to other already processed parameters.
|
||||||
|
|
||||||
Here an example for a ``--version`` flag:
|
Here an example for a ``--version`` flag:
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,7 @@ functional at least on a basic level even if everything is completely
|
||||||
broken.
|
broken.
|
||||||
|
|
||||||
What this means is that the :func:`echo` function applies some error
|
What this means is that the :func:`echo` function applies some error
|
||||||
correction in case the terminal is misconfigured instead of dying with an
|
correction in case the terminal is misconfigured instead of dying with a
|
||||||
:exc:`UnicodeError`.
|
:exc:`UnicodeError`.
|
||||||
|
|
||||||
The echo function also supports color and other styles in output. It
|
The echo function also supports color and other styles in output. It
|
||||||
|
|
|
@ -132,6 +132,8 @@ with the incomplete value.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
class EnvVarType(ParamType):
|
class EnvVarType(ParamType):
|
||||||
|
name = "envvar"
|
||||||
|
|
||||||
def shell_complete(self, ctx, param, incomplete):
|
def shell_complete(self, ctx, param, incomplete):
|
||||||
return [
|
return [
|
||||||
CompletionItem(name)
|
CompletionItem(name)
|
||||||
|
|
|
@ -9,4 +9,4 @@ Click Examples
|
||||||
through the wrong interpreter.
|
through the wrong interpreter.
|
||||||
|
|
||||||
For more information about this see the documentation:
|
For more information about this see the documentation:
|
||||||
https://click.palletsprojects.com/en/7.x/setuptools/
|
https://click.palletsprojects.com/setuptools/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
-r docs.in
|
-r docs.in
|
||||||
-r tests.in
|
-r tests.in
|
||||||
-r typing.in
|
-r typing.in
|
||||||
pip-tools
|
pip-compile-multi
|
||||||
pre-commit
|
pre-commit
|
||||||
tox
|
tox
|
||||||
|
|
|
@ -1,141 +1,58 @@
|
||||||
|
# SHA1:54b5b77ec8c7a0064ffa93b2fd16cb0130ba177c
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile
|
# This file is autogenerated by pip-compile-multi
|
||||||
# To update, run:
|
# To update, run:
|
||||||
#
|
#
|
||||||
# pip-compile requirements/dev.in
|
# pip-compile-multi
|
||||||
#
|
#
|
||||||
alabaster==0.7.12
|
-r docs.txt
|
||||||
# via sphinx
|
-r tests.txt
|
||||||
attrs==21.2.0
|
-r typing.txt
|
||||||
# via pytest
|
|
||||||
babel==2.9.1
|
|
||||||
# via sphinx
|
|
||||||
backports.entry-points-selectable==1.1.0
|
|
||||||
# via virtualenv
|
|
||||||
certifi==2021.5.30
|
|
||||||
# via requests
|
|
||||||
cfgv==3.3.1
|
cfgv==3.3.1
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
charset-normalizer==2.0.6
|
click==8.1.2
|
||||||
# via requests
|
|
||||||
click==8.0.1
|
|
||||||
# via pip-tools
|
|
||||||
distlib==0.3.3
|
|
||||||
# via virtualenv
|
|
||||||
docutils==0.16
|
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# pip-compile-multi
|
||||||
# sphinx-tabs
|
# pip-tools
|
||||||
filelock==3.3.0
|
distlib==0.3.4
|
||||||
|
# via virtualenv
|
||||||
|
filelock==3.6.0
|
||||||
# via
|
# via
|
||||||
# tox
|
# tox
|
||||||
# virtualenv
|
# virtualenv
|
||||||
identify==2.3.0
|
identify==2.5.0
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
idna==3.2
|
|
||||||
# via requests
|
|
||||||
imagesize==1.2.0
|
|
||||||
# via sphinx
|
|
||||||
iniconfig==1.1.1
|
|
||||||
# via pytest
|
|
||||||
jinja2==3.0.2
|
|
||||||
# via sphinx
|
|
||||||
markupsafe==2.0.1
|
|
||||||
# via jinja2
|
|
||||||
mypy-extensions==0.4.3
|
|
||||||
# via mypy
|
|
||||||
mypy==0.910
|
|
||||||
# via -r requirements/typing.in
|
|
||||||
nodeenv==1.6.0
|
nodeenv==1.6.0
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
packaging==21.0
|
pep517==0.12.0
|
||||||
# via
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
# pytest
|
|
||||||
# sphinx
|
|
||||||
# tox
|
|
||||||
pallets-sphinx-themes==2.0.1
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
pep517==0.11.0
|
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
pip-tools==6.3.0
|
pip-compile-multi==2.4.5
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
platformdirs==2.4.0
|
pip-tools==6.6.0
|
||||||
|
# via pip-compile-multi
|
||||||
|
platformdirs==2.5.2
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
pluggy==1.0.0
|
pre-commit==2.18.1
|
||||||
# via
|
|
||||||
# pytest
|
|
||||||
# tox
|
|
||||||
pre-commit==2.15.0
|
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
py==1.10.0
|
pyyaml==6.0
|
||||||
# via
|
|
||||||
# pytest
|
|
||||||
# tox
|
|
||||||
pygments==2.10.0
|
|
||||||
# via
|
|
||||||
# sphinx
|
|
||||||
# sphinx-tabs
|
|
||||||
pyparsing==2.4.7
|
|
||||||
# via packaging
|
|
||||||
pytest==6.2.5
|
|
||||||
# via -r requirements/tests.in
|
|
||||||
pytz==2021.3
|
|
||||||
# via babel
|
|
||||||
pyyaml==5.4.1
|
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
requests==2.26.0
|
|
||||||
# via sphinx
|
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
# via
|
# via
|
||||||
# tox
|
# tox
|
||||||
# virtualenv
|
# virtualenv
|
||||||
snowballstemmer==2.1.0
|
|
||||||
# via sphinx
|
|
||||||
sphinx-issues==1.2.0
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx-tabs==3.2.0
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx==4.2.0
|
|
||||||
# via
|
|
||||||
# -r requirements/docs.in
|
|
||||||
# pallets-sphinx-themes
|
|
||||||
# sphinx-issues
|
|
||||||
# sphinx-tabs
|
|
||||||
# sphinxcontrib-log-cabinet
|
|
||||||
sphinxcontrib-applehelp==1.0.2
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-devhelp==1.0.2
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-htmlhelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-jsmath==1.0.1
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-log-cabinet==1.0.1
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinxcontrib-qthelp==1.0.3
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-serializinghtml==1.1.5
|
|
||||||
# via sphinx
|
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
# via
|
# via
|
||||||
# mypy
|
|
||||||
# pre-commit
|
# pre-commit
|
||||||
# pytest
|
|
||||||
# tox
|
# tox
|
||||||
tomli==1.2.1
|
toposort==1.7
|
||||||
# via pep517
|
# via pip-compile-multi
|
||||||
tox==3.24.4
|
tox==3.25.0
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
typing-extensions==3.10.0.2
|
virtualenv==20.14.1
|
||||||
# via mypy
|
|
||||||
urllib3==1.26.7
|
|
||||||
# via requests
|
|
||||||
virtualenv==20.8.1
|
|
||||||
# via
|
# via
|
||||||
# pre-commit
|
# pre-commit
|
||||||
# tox
|
# tox
|
||||||
wheel==0.37.0
|
wheel==0.37.1
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
|
|
@ -1,58 +1,59 @@
|
||||||
|
# SHA1:34fd4ca6516e97c7348e6facdd9c4ebb68209d1c
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile
|
# This file is autogenerated by pip-compile-multi
|
||||||
# To update, run:
|
# To update, run:
|
||||||
#
|
#
|
||||||
# pip-compile requirements/docs.in
|
# pip-compile-multi
|
||||||
#
|
#
|
||||||
alabaster==0.7.12
|
alabaster==0.7.12
|
||||||
# via sphinx
|
# via sphinx
|
||||||
babel==2.9.1
|
babel==2.10.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
certifi==2021.5.30
|
certifi==2021.10.8
|
||||||
# via requests
|
# via requests
|
||||||
charset-normalizer==2.0.6
|
charset-normalizer==2.0.12
|
||||||
# via requests
|
# via requests
|
||||||
docutils==0.16
|
docutils==0.17.1
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# sphinx
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
idna==3.2
|
idna==3.3
|
||||||
# via requests
|
# via requests
|
||||||
imagesize==1.2.0
|
imagesize==1.3.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
jinja2==3.0.2
|
jinja2==3.1.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
markupsafe==2.0.1
|
markupsafe==2.1.1
|
||||||
# via jinja2
|
# via jinja2
|
||||||
packaging==21.0
|
packaging==21.3
|
||||||
# via
|
# via
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
# sphinx
|
# sphinx
|
||||||
pallets-sphinx-themes==2.0.1
|
pallets-sphinx-themes==2.0.2
|
||||||
# via -r requirements/docs.in
|
# via -r requirements/docs.in
|
||||||
pygments==2.10.0
|
pygments==2.12.0
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# sphinx
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
pyparsing==2.4.7
|
pyparsing==3.0.8
|
||||||
# via packaging
|
# via packaging
|
||||||
pytz==2021.3
|
pytz==2022.1
|
||||||
# via babel
|
# via babel
|
||||||
requests==2.26.0
|
requests==2.27.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.2.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinx-issues==1.2.0
|
sphinx==4.5.0
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx-tabs==3.2.0
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx==4.2.0
|
|
||||||
# via
|
# via
|
||||||
# -r requirements/docs.in
|
# -r requirements/docs.in
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
# sphinx-issues
|
# sphinx-issues
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
# sphinxcontrib-log-cabinet
|
# sphinxcontrib-log-cabinet
|
||||||
|
sphinx-issues==3.0.1
|
||||||
|
# via -r requirements/docs.in
|
||||||
|
sphinx-tabs==3.3.1
|
||||||
|
# via -r requirements/docs.in
|
||||||
sphinxcontrib-applehelp==1.0.2
|
sphinxcontrib-applehelp==1.0.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-devhelp==1.0.2
|
sphinxcontrib-devhelp==1.0.2
|
||||||
|
@ -67,8 +68,5 @@ sphinxcontrib-qthelp==1.0.3
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-serializinghtml==1.1.5
|
sphinxcontrib-serializinghtml==1.1.5
|
||||||
# via sphinx
|
# via sphinx
|
||||||
urllib3==1.26.7
|
urllib3==1.26.9
|
||||||
# via requests
|
# via requests
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
|
||||||
# setuptools
|
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
|
# SHA1:0eaa389e1fdb3a1917c0f987514bd561be5718ee
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile
|
# This file is autogenerated by pip-compile-multi
|
||||||
# To update, run:
|
# To update, run:
|
||||||
#
|
#
|
||||||
# pip-compile requirements/tests.in
|
# pip-compile-multi
|
||||||
#
|
#
|
||||||
attrs==21.2.0
|
attrs==21.4.0
|
||||||
# via pytest
|
# via pytest
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
# via pytest
|
# via pytest
|
||||||
packaging==21.0
|
packaging==21.3
|
||||||
# via pytest
|
# via pytest
|
||||||
pluggy==1.0.0
|
pluggy==1.0.0
|
||||||
# via pytest
|
# via pytest
|
||||||
py==1.10.0
|
py==1.11.0
|
||||||
# via pytest
|
# via pytest
|
||||||
pyparsing==2.4.7
|
pyparsing==3.0.8
|
||||||
# via packaging
|
# via packaging
|
||||||
pytest==6.2.5
|
pytest==7.1.2
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
toml==0.10.2
|
tomli==2.0.1
|
||||||
# via pytest
|
# via pytest
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
# SHA1:7983aaa01d64547827c20395d77e248c41b2572f
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile
|
# This file is autogenerated by pip-compile-multi
|
||||||
# To update, run:
|
# To update, run:
|
||||||
#
|
#
|
||||||
# pip-compile requirements/typing.in
|
# pip-compile-multi
|
||||||
#
|
#
|
||||||
|
mypy==0.950
|
||||||
|
# via -r requirements/typing.in
|
||||||
mypy-extensions==0.4.3
|
mypy-extensions==0.4.3
|
||||||
# via mypy
|
# via mypy
|
||||||
mypy==0.910
|
tomli==2.0.1
|
||||||
# via -r requirements/typing.in
|
|
||||||
toml==0.10.2
|
|
||||||
# via mypy
|
# via mypy
|
||||||
typing-extensions==3.10.0.2
|
typing-extensions==4.2.0
|
||||||
# via mypy
|
# via mypy
|
||||||
|
|
15
setup.cfg
15
setup.cfg
|
@ -29,8 +29,8 @@ classifiers =
|
||||||
[options]
|
[options]
|
||||||
packages = find:
|
packages = find:
|
||||||
package_dir = = src
|
package_dir = = src
|
||||||
include_package_data = true
|
include_package_data = True
|
||||||
python_requires = >= 3.6
|
python_requires = >= 3.7
|
||||||
# Dependencies are in setup.py for GitHub's dependency graph.
|
# Dependencies are in setup.py for GitHub's dependency graph.
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
|
@ -42,14 +42,14 @@ filterwarnings =
|
||||||
error
|
error
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
branch = true
|
branch = True
|
||||||
source =
|
source =
|
||||||
click
|
click
|
||||||
tests
|
tests
|
||||||
|
|
||||||
[coverage:paths]
|
[coverage:paths]
|
||||||
source =
|
source =
|
||||||
click
|
src
|
||||||
*/site-packages
|
*/site-packages
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
|
@ -57,7 +57,7 @@ source =
|
||||||
# E = pycodestyle errors
|
# E = pycodestyle errors
|
||||||
# F = flake8 pyflakes
|
# F = flake8 pyflakes
|
||||||
# W = pycodestyle warnings
|
# W = pycodestyle warnings
|
||||||
# B9 = bugbear opinions,
|
# B9 = bugbear opinions
|
||||||
# ISC = implicit str concat
|
# ISC = implicit str concat
|
||||||
select = B, E, F, W, B9, ISC
|
select = B, E, F, W, B9, ISC
|
||||||
ignore =
|
ignore =
|
||||||
|
@ -72,12 +72,13 @@ ignore =
|
||||||
# up to 88 allowed by bugbear B950
|
# up to 88 allowed by bugbear B950
|
||||||
max-line-length = 80
|
max-line-length = 80
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
# __init__ module exports names
|
# __init__ exports names
|
||||||
src/click/__init__.py: F401
|
src/click/__init__.py: F401
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
files = src/click
|
files = src/click
|
||||||
python_version = 3.6
|
python_version = 3.7
|
||||||
|
show_error_codes = True
|
||||||
disallow_subclassing_any = True
|
disallow_subclassing_any = True
|
||||||
disallow_untyped_calls = True
|
disallow_untyped_calls = True
|
||||||
disallow_untyped_defs = True
|
disallow_untyped_defs = True
|
||||||
|
|
|
@ -41,7 +41,6 @@ from .termui import clear as clear
|
||||||
from .termui import confirm as confirm
|
from .termui import confirm as confirm
|
||||||
from .termui import echo_via_pager as echo_via_pager
|
from .termui import echo_via_pager as echo_via_pager
|
||||||
from .termui import edit as edit
|
from .termui import edit as edit
|
||||||
from .termui import get_terminal_size as get_terminal_size
|
|
||||||
from .termui import getchar as getchar
|
from .termui import getchar as getchar
|
||||||
from .termui import launch as launch
|
from .termui import launch as launch
|
||||||
from .termui import pause as pause
|
from .termui import pause as pause
|
||||||
|
@ -68,8 +67,7 @@ from .utils import echo as echo
|
||||||
from .utils import format_filename as format_filename
|
from .utils import format_filename as format_filename
|
||||||
from .utils import get_app_dir as get_app_dir
|
from .utils import get_app_dir as get_app_dir
|
||||||
from .utils import get_binary_stream as get_binary_stream
|
from .utils import get_binary_stream as get_binary_stream
|
||||||
from .utils import get_os_args as get_os_args
|
|
||||||
from .utils import get_text_stream as get_text_stream
|
from .utils import get_text_stream as get_text_stream
|
||||||
from .utils import open_file as open_file
|
from .utils import open_file as open_file
|
||||||
|
|
||||||
__version__ = "8.0.3"
|
__version__ = "8.1.3"
|
||||||
|
|
|
@ -388,9 +388,9 @@ def open_stream(
|
||||||
) -> t.Tuple[t.IO, bool]:
|
) -> t.Tuple[t.IO, bool]:
|
||||||
binary = "b" in mode
|
binary = "b" in mode
|
||||||
|
|
||||||
# Standard streams first. These are simple because they don't need
|
# Standard streams first. These are simple because they ignore the
|
||||||
# special handling for the atomic flag. It's entirely ignored.
|
# atomic flag. Use fsdecode to handle Path("-").
|
||||||
if filename == "-":
|
if os.fsdecode(filename) == "-":
|
||||||
if any(m in mode for m in ["w", "a", "x"]):
|
if any(m in mode for m in ["w", "a", "x"]):
|
||||||
if binary:
|
if binary:
|
||||||
return get_binary_stdout(), False
|
return get_binary_stdout(), False
|
||||||
|
@ -561,7 +561,6 @@ if sys.platform.startswith("win") and WIN:
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def _get_argv_encoding() -> str:
|
def _get_argv_encoding() -> str:
|
||||||
|
|
|
@ -675,7 +675,6 @@ if WIN:
|
||||||
_translate_ch_to_exc(rv)
|
_translate_ch_to_exc(rv)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
import tty
|
import tty
|
||||||
import termios
|
import termios
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
import codecs
|
|
||||||
import os
|
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
|
|
||||||
def _verify_python_env() -> None:
|
|
||||||
"""Ensures that the environment is good for Unicode."""
|
|
||||||
try:
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
fs_enc = codecs.lookup(getpreferredencoding()).name
|
|
||||||
except Exception:
|
|
||||||
fs_enc = "ascii"
|
|
||||||
|
|
||||||
if fs_enc != "ascii":
|
|
||||||
return
|
|
||||||
|
|
||||||
extra = [
|
|
||||||
_(
|
|
||||||
"Click will abort further execution because Python was"
|
|
||||||
" configured to use ASCII as encoding for the environment."
|
|
||||||
" Consult https://click.palletsprojects.com/unicode-support/"
|
|
||||||
" for mitigation steps."
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if os.name == "posix":
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
try:
|
|
||||||
rv = subprocess.Popen(
|
|
||||||
["locale", "-a"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
encoding="ascii",
|
|
||||||
errors="replace",
|
|
||||||
).communicate()[0]
|
|
||||||
except OSError:
|
|
||||||
rv = ""
|
|
||||||
|
|
||||||
good_locales = set()
|
|
||||||
has_c_utf8 = False
|
|
||||||
|
|
||||||
for line in rv.splitlines():
|
|
||||||
locale = line.strip()
|
|
||||||
|
|
||||||
if locale.lower().endswith((".utf-8", ".utf8")):
|
|
||||||
good_locales.add(locale)
|
|
||||||
|
|
||||||
if locale.lower() in ("c.utf8", "c.utf-8"):
|
|
||||||
has_c_utf8 = True
|
|
||||||
|
|
||||||
if not good_locales:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"Additional information: on this system no suitable"
|
|
||||||
" UTF-8 locales were discovered. This most likely"
|
|
||||||
" requires resolving by reconfiguring the locale"
|
|
||||||
" system."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif has_c_utf8:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"This system supports the C.UTF-8 locale which is"
|
|
||||||
" recommended. You might be able to resolve your"
|
|
||||||
" issue by exporting the following environment"
|
|
||||||
" variables:"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
extra.append(" export LC_ALL=C.UTF-8\n export LANG=C.UTF-8")
|
|
||||||
else:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"This system lists some UTF-8 supporting locales"
|
|
||||||
" that you can pick from. The following suitable"
|
|
||||||
" locales were discovered: {locales}"
|
|
||||||
).format(locales=", ".join(sorted(good_locales)))
|
|
||||||
)
|
|
||||||
|
|
||||||
bad_locale = None
|
|
||||||
|
|
||||||
for env_locale in os.environ.get("LC_ALL"), os.environ.get("LANG"):
|
|
||||||
if env_locale and env_locale.lower().endswith((".utf-8", ".utf8")):
|
|
||||||
bad_locale = env_locale
|
|
||||||
|
|
||||||
if env_locale is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
if bad_locale is not None:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"Click discovered that you exported a UTF-8 locale"
|
|
||||||
" but the locale system could not pick up from it"
|
|
||||||
" because it does not exist. The exported locale is"
|
|
||||||
" {locale!r} but it is not supported."
|
|
||||||
).format(locale=bad_locale)
|
|
||||||
)
|
|
||||||
|
|
||||||
raise RuntimeError("\n\n".join(extra))
|
|
|
@ -1,8 +1,8 @@
|
||||||
import enum
|
import enum
|
||||||
import errno
|
import errno
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import typing
|
|
||||||
import typing as t
|
import typing as t
|
||||||
from collections import abc
|
from collections import abc
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
@ -14,7 +14,6 @@ from gettext import ngettext
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
|
||||||
from . import types
|
from . import types
|
||||||
from ._unicodefun import _verify_python_env
|
|
||||||
from .exceptions import Abort
|
from .exceptions import Abort
|
||||||
from .exceptions import BadParameter
|
from .exceptions import BadParameter
|
||||||
from .exceptions import ClickException
|
from .exceptions import ClickException
|
||||||
|
@ -224,9 +223,14 @@ class Context:
|
||||||
codes are used in texts that Click prints which is by
|
codes are used in texts that Click prints which is by
|
||||||
default not the case. This for instance would affect
|
default not the case. This for instance would affect
|
||||||
help output.
|
help output.
|
||||||
:param show_default: Show defaults for all options. If not set,
|
:param show_default: Show the default value for commands. If this
|
||||||
defaults to the value from a parent context. Overrides an
|
value is not set, it defaults to the value from the parent
|
||||||
option's ``show_default`` argument.
|
context. ``Command.show_default`` overrides this default for the
|
||||||
|
specific command.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
The ``show_default`` parameter is overridden by
|
||||||
|
``Command.show_default``, instead of the other way around.
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
.. versionchanged:: 8.0
|
||||||
The ``show_default`` parameter defaults to the value from the
|
The ``show_default`` parameter defaults to the value from the
|
||||||
|
@ -288,6 +292,8 @@ class Context:
|
||||||
#: must be never propagated to another arguments. This is used
|
#: must be never propagated to another arguments. This is used
|
||||||
#: to implement nested parsing.
|
#: to implement nested parsing.
|
||||||
self.protected_args: t.List[str] = []
|
self.protected_args: t.List[str] = []
|
||||||
|
#: the collected prefixes of the command's options.
|
||||||
|
self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set()
|
||||||
|
|
||||||
if obj is None and parent is not None:
|
if obj is None and parent is not None:
|
||||||
obj = parent.obj
|
obj = parent.obj
|
||||||
|
@ -632,13 +638,13 @@ class Context:
|
||||||
self.obj = rv = object_type()
|
self.obj = rv = object_type()
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def lookup_default(
|
def lookup_default(
|
||||||
self, name: str, call: "te.Literal[True]" = True
|
self, name: str, call: "te.Literal[True]" = True
|
||||||
) -> t.Optional[t.Any]:
|
) -> t.Optional[t.Any]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def lookup_default(
|
def lookup_default(
|
||||||
self, name: str, call: "te.Literal[False]" = ...
|
self, name: str, call: "te.Literal[False]" = ...
|
||||||
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
||||||
|
@ -956,7 +962,7 @@ class BaseCommand:
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def main(
|
def main(
|
||||||
self,
|
self,
|
||||||
args: t.Optional[t.Sequence[str]] = None,
|
args: t.Optional[t.Sequence[str]] = None,
|
||||||
|
@ -967,7 +973,7 @@ class BaseCommand:
|
||||||
) -> "te.NoReturn":
|
) -> "te.NoReturn":
|
||||||
...
|
...
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def main(
|
def main(
|
||||||
self,
|
self,
|
||||||
args: t.Optional[t.Sequence[str]] = None,
|
args: t.Optional[t.Sequence[str]] = None,
|
||||||
|
@ -1029,10 +1035,6 @@ class BaseCommand:
|
||||||
.. versionchanged:: 3.0
|
.. versionchanged:: 3.0
|
||||||
Added the ``standalone_mode`` parameter.
|
Added the ``standalone_mode`` parameter.
|
||||||
"""
|
"""
|
||||||
# Verify that the environment is configured correctly, or reject
|
|
||||||
# further execution to avoid a broken script.
|
|
||||||
_verify_python_env()
|
|
||||||
|
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
@ -1133,13 +1135,6 @@ class Command(BaseCommand):
|
||||||
Click. A basic command handles command line parsing and might dispatch
|
Click. A basic command handles command line parsing and might dispatch
|
||||||
more parsing to commands nested below it.
|
more parsing to commands nested below it.
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
Added the `context_settings` parameter.
|
|
||||||
.. versionchanged:: 8.0
|
|
||||||
Added repr showing the command name
|
|
||||||
.. versionchanged:: 7.1
|
|
||||||
Added the `no_args_is_help` parameter.
|
|
||||||
|
|
||||||
:param name: the name of the command to use unless a group overrides it.
|
:param name: the name of the command to use unless a group overrides it.
|
||||||
:param context_settings: an optional dictionary with defaults that are
|
:param context_settings: an optional dictionary with defaults that are
|
||||||
passed to the context object.
|
passed to the context object.
|
||||||
|
@ -1161,6 +1156,20 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
:param deprecated: issues a message indicating that
|
:param deprecated: issues a message indicating that
|
||||||
the command is deprecated.
|
the command is deprecated.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
``help``, ``epilog``, and ``short_help`` are stored unprocessed,
|
||||||
|
all formatting is done when outputting help text, not at init,
|
||||||
|
and is done even if not using the ``@command`` decorator.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Added a ``repr`` showing the command name.
|
||||||
|
|
||||||
|
.. versionchanged:: 7.1
|
||||||
|
Added the ``no_args_is_help`` parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Added the ``context_settings`` parameter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -1186,12 +1195,6 @@ class Command(BaseCommand):
|
||||||
#: should show up in the help page and execute. Eager parameters
|
#: should show up in the help page and execute. Eager parameters
|
||||||
#: will automatically be handled before non eager ones.
|
#: will automatically be handled before non eager ones.
|
||||||
self.params: t.List["Parameter"] = params or []
|
self.params: t.List["Parameter"] = params or []
|
||||||
|
|
||||||
# if a form feed (page break) is found in the help text, truncate help
|
|
||||||
# text to the content preceding the first form feed
|
|
||||||
if help and "\f" in help:
|
|
||||||
help = help.split("\f", 1)[0]
|
|
||||||
|
|
||||||
self.help = help
|
self.help = help
|
||||||
self.epilog = epilog
|
self.epilog = epilog
|
||||||
self.options_metavar = options_metavar
|
self.options_metavar = options_metavar
|
||||||
|
@ -1299,10 +1302,12 @@ class Command(BaseCommand):
|
||||||
"""Gets short help for the command or makes it by shortening the
|
"""Gets short help for the command or makes it by shortening the
|
||||||
long help string.
|
long help string.
|
||||||
"""
|
"""
|
||||||
text = self.short_help or ""
|
if self.short_help:
|
||||||
|
text = inspect.cleandoc(self.short_help)
|
||||||
if not text and self.help:
|
elif self.help:
|
||||||
text = make_default_short_help(self.help, limit)
|
text = make_default_short_help(self.help, limit)
|
||||||
|
else:
|
||||||
|
text = ""
|
||||||
|
|
||||||
if self.deprecated:
|
if self.deprecated:
|
||||||
text = _("(Deprecated) {text}").format(text=text)
|
text = _("(Deprecated) {text}").format(text=text)
|
||||||
|
@ -1328,12 +1333,13 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None:
|
def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None:
|
||||||
"""Writes the help text to the formatter if it exists."""
|
"""Writes the help text to the formatter if it exists."""
|
||||||
text = self.help or ""
|
text = self.help if self.help is not None else ""
|
||||||
|
|
||||||
if self.deprecated:
|
if self.deprecated:
|
||||||
text = _("(Deprecated) {text}").format(text=text)
|
text = _("(Deprecated) {text}").format(text=text)
|
||||||
|
|
||||||
if text:
|
if text:
|
||||||
|
text = inspect.cleandoc(text).partition("\f")[0]
|
||||||
formatter.write_paragraph()
|
formatter.write_paragraph()
|
||||||
|
|
||||||
with formatter.indentation():
|
with formatter.indentation():
|
||||||
|
@ -1354,9 +1360,11 @@ class Command(BaseCommand):
|
||||||
def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None:
|
def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None:
|
||||||
"""Writes the epilog into the formatter if it exists."""
|
"""Writes the epilog into the formatter if it exists."""
|
||||||
if self.epilog:
|
if self.epilog:
|
||||||
|
epilog = inspect.cleandoc(self.epilog)
|
||||||
formatter.write_paragraph()
|
formatter.write_paragraph()
|
||||||
|
|
||||||
with formatter.indentation():
|
with formatter.indentation():
|
||||||
formatter.write_text(self.epilog)
|
formatter.write_text(epilog)
|
||||||
|
|
||||||
def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]:
|
def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]:
|
||||||
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||||
|
@ -1379,6 +1387,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.args = args
|
ctx.args = args
|
||||||
|
ctx._opt_prefixes.update(parser._opt_prefixes)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def invoke(self, ctx: Context) -> t.Any:
|
def invoke(self, ctx: Context) -> t.Any:
|
||||||
|
@ -1568,17 +1577,6 @@ class MultiCommand(Command):
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def resultcallback(self, replace: bool = False) -> t.Callable[[F], F]:
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'resultcallback' has been renamed to 'result_callback'."
|
|
||||||
" The old name will be removed in Click 8.1.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return self.result_callback(replace=replace)
|
|
||||||
|
|
||||||
def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None:
|
def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None:
|
||||||
"""Extra format methods for multi methods that adds all the commands
|
"""Extra format methods for multi methods that adds all the commands
|
||||||
after the options.
|
after the options.
|
||||||
|
@ -1631,11 +1629,11 @@ class MultiCommand(Command):
|
||||||
if not ctx.protected_args:
|
if not ctx.protected_args:
|
||||||
if self.invoke_without_command:
|
if self.invoke_without_command:
|
||||||
# No subcommand was invoked, so the result callback is
|
# No subcommand was invoked, so the result callback is
|
||||||
# invoked with None for regular groups, or an empty list
|
# invoked with the group return value for regular
|
||||||
# for chained groups.
|
# groups, or an empty list for chained groups.
|
||||||
with ctx:
|
with ctx:
|
||||||
super().invoke(ctx)
|
rv = super().invoke(ctx)
|
||||||
return _process_result([] if self.chain else None)
|
return _process_result([] if self.chain else rv)
|
||||||
ctx.fail(_("Missing command."))
|
ctx.fail(_("Missing command."))
|
||||||
|
|
||||||
# Fetch args back out
|
# Fetch args back out
|
||||||
|
@ -1811,9 +1809,19 @@ class Group(MultiCommand):
|
||||||
_check_multicommand(self, name, cmd, register=True)
|
_check_multicommand(self, name, cmd, register=True)
|
||||||
self.commands[name] = cmd
|
self.commands[name] = cmd
|
||||||
|
|
||||||
|
@t.overload
|
||||||
|
def command(self, __func: t.Callable[..., t.Any]) -> Command:
|
||||||
|
...
|
||||||
|
|
||||||
|
@t.overload
|
||||||
def command(
|
def command(
|
||||||
self, *args: t.Any, **kwargs: t.Any
|
self, *args: t.Any, **kwargs: t.Any
|
||||||
) -> t.Callable[[t.Callable[..., t.Any]], Command]:
|
) -> t.Callable[[t.Callable[..., t.Any]], Command]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def command(
|
||||||
|
self, *args: t.Any, **kwargs: t.Any
|
||||||
|
) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]:
|
||||||
"""A shortcut decorator for declaring and attaching a command to
|
"""A shortcut decorator for declaring and attaching a command to
|
||||||
the group. This takes the same arguments as :func:`command` and
|
the group. This takes the same arguments as :func:`command` and
|
||||||
immediately registers the created command with this group by
|
immediately registers the created command with this group by
|
||||||
|
@ -1822,24 +1830,49 @@ class Group(MultiCommand):
|
||||||
To customize the command class used, set the
|
To customize the command class used, set the
|
||||||
:attr:`command_class` attribute.
|
:attr:`command_class` attribute.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
This decorator can be applied without parentheses.
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
.. versionchanged:: 8.0
|
||||||
Added the :attr:`command_class` attribute.
|
Added the :attr:`command_class` attribute.
|
||||||
"""
|
"""
|
||||||
from .decorators import command
|
from .decorators import command
|
||||||
|
|
||||||
if self.command_class is not None and "cls" not in kwargs:
|
if self.command_class and kwargs.get("cls") is None:
|
||||||
kwargs["cls"] = self.command_class
|
kwargs["cls"] = self.command_class
|
||||||
|
|
||||||
|
func: t.Optional[t.Callable] = None
|
||||||
|
|
||||||
|
if args and callable(args[0]):
|
||||||
|
assert (
|
||||||
|
len(args) == 1 and not kwargs
|
||||||
|
), "Use 'command(**kwargs)(callable)' to provide arguments."
|
||||||
|
(func,) = args
|
||||||
|
args = ()
|
||||||
|
|
||||||
def decorator(f: t.Callable[..., t.Any]) -> Command:
|
def decorator(f: t.Callable[..., t.Any]) -> Command:
|
||||||
cmd = command(*args, **kwargs)(f)
|
cmd: Command = command(*args, **kwargs)(f)
|
||||||
self.add_command(cmd)
|
self.add_command(cmd)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
if func is not None:
|
||||||
|
return decorator(func)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@t.overload
|
||||||
|
def group(self, __func: t.Callable[..., t.Any]) -> "Group":
|
||||||
|
...
|
||||||
|
|
||||||
|
@t.overload
|
||||||
def group(
|
def group(
|
||||||
self, *args: t.Any, **kwargs: t.Any
|
self, *args: t.Any, **kwargs: t.Any
|
||||||
) -> t.Callable[[t.Callable[..., t.Any]], "Group"]:
|
) -> t.Callable[[t.Callable[..., t.Any]], "Group"]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def group(
|
||||||
|
self, *args: t.Any, **kwargs: t.Any
|
||||||
|
) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]:
|
||||||
"""A shortcut decorator for declaring and attaching a group to
|
"""A shortcut decorator for declaring and attaching a group to
|
||||||
the group. This takes the same arguments as :func:`group` and
|
the group. This takes the same arguments as :func:`group` and
|
||||||
immediately registers the created group with this group by
|
immediately registers the created group with this group by
|
||||||
|
@ -1848,22 +1881,37 @@ class Group(MultiCommand):
|
||||||
To customize the group class used, set the :attr:`group_class`
|
To customize the group class used, set the :attr:`group_class`
|
||||||
attribute.
|
attribute.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
This decorator can be applied without parentheses.
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
.. versionchanged:: 8.0
|
||||||
Added the :attr:`group_class` attribute.
|
Added the :attr:`group_class` attribute.
|
||||||
"""
|
"""
|
||||||
from .decorators import group
|
from .decorators import group
|
||||||
|
|
||||||
if self.group_class is not None and "cls" not in kwargs:
|
func: t.Optional[t.Callable] = None
|
||||||
|
|
||||||
|
if args and callable(args[0]):
|
||||||
|
assert (
|
||||||
|
len(args) == 1 and not kwargs
|
||||||
|
), "Use 'group(**kwargs)(callable)' to provide arguments."
|
||||||
|
(func,) = args
|
||||||
|
args = ()
|
||||||
|
|
||||||
|
if self.group_class is not None and kwargs.get("cls") is None:
|
||||||
if self.group_class is type:
|
if self.group_class is type:
|
||||||
kwargs["cls"] = type(self)
|
kwargs["cls"] = type(self)
|
||||||
else:
|
else:
|
||||||
kwargs["cls"] = self.group_class
|
kwargs["cls"] = self.group_class
|
||||||
|
|
||||||
def decorator(f: t.Callable[..., t.Any]) -> "Group":
|
def decorator(f: t.Callable[..., t.Any]) -> "Group":
|
||||||
cmd = group(*args, **kwargs)(f)
|
cmd: Group = group(*args, **kwargs)(f)
|
||||||
self.add_command(cmd)
|
self.add_command(cmd)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
if func is not None:
|
||||||
|
return decorator(func)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
|
def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
|
||||||
|
@ -2020,11 +2068,6 @@ class Parameter:
|
||||||
t.Union[t.List["CompletionItem"], t.List[str]],
|
t.Union[t.List["CompletionItem"], t.List[str]],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
autocompletion: t.Optional[
|
|
||||||
t.Callable[
|
|
||||||
[Context, t.List[str], str], t.List[t.Union[t.Tuple[str, str], str]]
|
|
||||||
]
|
|
||||||
] = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name, self.opts, self.secondary_opts = self._parse_decls(
|
self.name, self.opts, self.secondary_opts = self._parse_decls(
|
||||||
param_decls or (), expose_value
|
param_decls or (), expose_value
|
||||||
|
@ -2048,36 +2091,6 @@ class Parameter:
|
||||||
self.is_eager = is_eager
|
self.is_eager = is_eager
|
||||||
self.metavar = metavar
|
self.metavar = metavar
|
||||||
self.envvar = envvar
|
self.envvar = envvar
|
||||||
|
|
||||||
if autocompletion is not None:
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'autocompletion' is renamed to 'shell_complete'. The old name is"
|
|
||||||
" deprecated and will be removed in Click 8.1. See the docs about"
|
|
||||||
" 'Parameter' for information about new behavior.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
def shell_complete(
|
|
||||||
ctx: Context, param: "Parameter", incomplete: str
|
|
||||||
) -> t.List["CompletionItem"]:
|
|
||||||
from click.shell_completion import CompletionItem
|
|
||||||
|
|
||||||
out = []
|
|
||||||
|
|
||||||
for c in autocompletion(ctx, [], incomplete): # type: ignore
|
|
||||||
if isinstance(c, tuple):
|
|
||||||
c = CompletionItem(c[0], help=c[1])
|
|
||||||
elif isinstance(c, str):
|
|
||||||
c = CompletionItem(c)
|
|
||||||
|
|
||||||
if c.value.startswith(incomplete):
|
|
||||||
out.append(c)
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
self._custom_shell_complete = shell_complete
|
self._custom_shell_complete = shell_complete
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
@ -2172,13 +2185,13 @@ class Parameter:
|
||||||
|
|
||||||
return metavar
|
return metavar
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def get_default(
|
def get_default(
|
||||||
self, ctx: Context, call: "te.Literal[True]" = True
|
self, ctx: Context, call: "te.Literal[True]" = True
|
||||||
) -> t.Optional[t.Any]:
|
) -> t.Optional[t.Any]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def get_default(
|
def get_default(
|
||||||
self, ctx: Context, call: bool = ...
|
self, ctx: Context, call: bool = ...
|
||||||
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
||||||
|
@ -2399,25 +2412,27 @@ class Option(Parameter):
|
||||||
|
|
||||||
All other parameters are passed onwards to the parameter constructor.
|
All other parameters are passed onwards to the parameter constructor.
|
||||||
|
|
||||||
:param show_default: controls if the default value should be shown on the
|
:param show_default: Show the default value for this option in its
|
||||||
help page. Normally, defaults are not shown. If this
|
help text. Values are not shown by default, unless
|
||||||
value is a string, it shows the string instead of the
|
:attr:`Context.show_default` is ``True``. If this value is a
|
||||||
value. This is particularly useful for dynamic options.
|
string, it shows that string in parentheses instead of the
|
||||||
:param show_envvar: controls if an environment variable should be shown on
|
actual value. This is particularly useful for dynamic options.
|
||||||
the help page. Normally, environment variables
|
For single option boolean flags, the default remains hidden if
|
||||||
are not shown.
|
its value is ``False``.
|
||||||
:param prompt: if set to `True` or a non empty string then the user will be
|
:param show_envvar: Controls if an environment variable should be
|
||||||
prompted for input. If set to `True` the prompt will be the
|
shown on the help page. Normally, environment variables are not
|
||||||
option name capitalized.
|
shown.
|
||||||
|
:param prompt: If set to ``True`` or a non empty string then the
|
||||||
|
user will be prompted for input. If set to ``True`` the prompt
|
||||||
|
will be the option name capitalized.
|
||||||
:param confirmation_prompt: Prompt a second time to confirm the
|
:param confirmation_prompt: Prompt a second time to confirm the
|
||||||
value if it was prompted for. Can be set to a string instead of
|
value if it was prompted for. Can be set to a string instead of
|
||||||
``True`` to customize the message.
|
``True`` to customize the message.
|
||||||
:param prompt_required: If set to ``False``, the user will be
|
:param prompt_required: If set to ``False``, the user will be
|
||||||
prompted for input only when the option was specified as a flag
|
prompted for input only when the option was specified as a flag
|
||||||
without a value.
|
without a value.
|
||||||
:param hide_input: if this is `True` then the input on the prompt will be
|
:param hide_input: If this is ``True`` then the input on the prompt
|
||||||
hidden from the user. This is useful for password
|
will be hidden from the user. This is useful for password input.
|
||||||
input.
|
|
||||||
:param is_flag: forces this option to act as a flag. The default is
|
:param is_flag: forces this option to act as a flag. The default is
|
||||||
auto detection.
|
auto detection.
|
||||||
:param flag_value: which value should be used for this flag if it's
|
:param flag_value: which value should be used for this flag if it's
|
||||||
|
@ -2435,6 +2450,18 @@ class Option(Parameter):
|
||||||
:param help: the help string.
|
:param help: the help string.
|
||||||
:param hidden: hide this option from help outputs.
|
:param hidden: hide this option from help outputs.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1.0
|
||||||
|
Help text indentation is cleaned here instead of only in the
|
||||||
|
``@option`` decorator.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1.0
|
||||||
|
The ``show_default`` parameter overrides
|
||||||
|
``Context.show_default``.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1.0
|
||||||
|
The default of a single option boolean flag is not shown if the
|
||||||
|
default value is ``False``.
|
||||||
|
|
||||||
.. versionchanged:: 8.0.1
|
.. versionchanged:: 8.0.1
|
||||||
``type`` is detected from ``flag_value`` if given.
|
``type`` is detected from ``flag_value`` if given.
|
||||||
"""
|
"""
|
||||||
|
@ -2444,7 +2471,7 @@ class Option(Parameter):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
param_decls: t.Optional[t.Sequence[str]] = None,
|
param_decls: t.Optional[t.Sequence[str]] = None,
|
||||||
show_default: t.Union[bool, str] = False,
|
show_default: t.Union[bool, str, None] = None,
|
||||||
prompt: t.Union[bool, str] = False,
|
prompt: t.Union[bool, str] = False,
|
||||||
confirmation_prompt: t.Union[bool, str] = False,
|
confirmation_prompt: t.Union[bool, str] = False,
|
||||||
prompt_required: bool = True,
|
prompt_required: bool = True,
|
||||||
|
@ -2461,6 +2488,9 @@ class Option(Parameter):
|
||||||
show_envvar: bool = False,
|
show_envvar: bool = False,
|
||||||
**attrs: t.Any,
|
**attrs: t.Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if help:
|
||||||
|
help = inspect.cleandoc(help)
|
||||||
|
|
||||||
default_is_missing = "default" not in attrs
|
default_is_missing = "default" not in attrs
|
||||||
super().__init__(param_decls, type=type, multiple=multiple, **attrs)
|
super().__init__(param_decls, type=type, multiple=multiple, **attrs)
|
||||||
|
|
||||||
|
@ -2472,7 +2502,7 @@ class Option(Parameter):
|
||||||
elif prompt is False:
|
elif prompt is False:
|
||||||
prompt_text = None
|
prompt_text = None
|
||||||
else:
|
else:
|
||||||
prompt_text = t.cast(str, prompt)
|
prompt_text = prompt
|
||||||
|
|
||||||
self.prompt = prompt_text
|
self.prompt = prompt_text
|
||||||
self.confirmation_prompt = confirmation_prompt
|
self.confirmation_prompt = confirmation_prompt
|
||||||
|
@ -2499,7 +2529,7 @@ class Option(Parameter):
|
||||||
# flag if flag_value is set.
|
# flag if flag_value is set.
|
||||||
self._flag_needs_value = flag_value is not None
|
self._flag_needs_value = flag_value is not None
|
||||||
|
|
||||||
if is_flag and default_is_missing:
|
if is_flag and default_is_missing and not self.required:
|
||||||
self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False
|
self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False
|
||||||
|
|
||||||
if flag_value is None:
|
if flag_value is None:
|
||||||
|
@ -2550,6 +2580,9 @@ class Option(Parameter):
|
||||||
if self.is_flag:
|
if self.is_flag:
|
||||||
raise TypeError("'count' is not valid with 'is_flag'.")
|
raise TypeError("'count' is not valid with 'is_flag'.")
|
||||||
|
|
||||||
|
if self.multiple and self.is_flag:
|
||||||
|
raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.")
|
||||||
|
|
||||||
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
||||||
info_dict = super().to_info_dict()
|
info_dict = super().to_info_dict()
|
||||||
info_dict.update(
|
info_dict.update(
|
||||||
|
@ -2711,16 +2744,23 @@ class Option(Parameter):
|
||||||
finally:
|
finally:
|
||||||
ctx.resilient_parsing = resilient
|
ctx.resilient_parsing = resilient
|
||||||
|
|
||||||
show_default_is_str = isinstance(self.show_default, str)
|
show_default = False
|
||||||
|
show_default_is_str = False
|
||||||
|
|
||||||
if show_default_is_str or (
|
if self.show_default is not None:
|
||||||
default_value is not None and (self.show_default or ctx.show_default)
|
if isinstance(self.show_default, str):
|
||||||
):
|
show_default_is_str = show_default = True
|
||||||
|
else:
|
||||||
|
show_default = self.show_default
|
||||||
|
elif ctx.show_default is not None:
|
||||||
|
show_default = ctx.show_default
|
||||||
|
|
||||||
|
if show_default_is_str or (show_default and (default_value is not None)):
|
||||||
if show_default_is_str:
|
if show_default_is_str:
|
||||||
default_string = f"({self.show_default})"
|
default_string = f"({self.show_default})"
|
||||||
elif isinstance(default_value, (list, tuple)):
|
elif isinstance(default_value, (list, tuple)):
|
||||||
default_string = ", ".join(str(d) for d in default_value)
|
default_string = ", ".join(str(d) for d in default_value)
|
||||||
elif callable(default_value):
|
elif inspect.isfunction(default_value):
|
||||||
default_string = _("(dynamic)")
|
default_string = _("(dynamic)")
|
||||||
elif self.is_bool_flag and self.secondary_opts:
|
elif self.is_bool_flag and self.secondary_opts:
|
||||||
# For boolean flags that have distinct True/False opts,
|
# For boolean flags that have distinct True/False opts,
|
||||||
|
@ -2728,6 +2768,8 @@ class Option(Parameter):
|
||||||
default_string = split_opt(
|
default_string = split_opt(
|
||||||
(self.opts if self.default else self.secondary_opts)[0]
|
(self.opts if self.default else self.secondary_opts)[0]
|
||||||
)[1]
|
)[1]
|
||||||
|
elif self.is_bool_flag and not self.secondary_opts and not default_value:
|
||||||
|
default_string = ""
|
||||||
else:
|
else:
|
||||||
default_string = str(default_value)
|
default_string = str(default_value)
|
||||||
|
|
||||||
|
@ -2753,13 +2795,13 @@ class Option(Parameter):
|
||||||
|
|
||||||
return ("; " if any_prefix_is_slash else " / ").join(rv), help
|
return ("; " if any_prefix_is_slash else " / ").join(rv), help
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def get_default(
|
def get_default(
|
||||||
self, ctx: Context, call: "te.Literal[True]" = True
|
self, ctx: Context, call: "te.Literal[True]" = True
|
||||||
) -> t.Optional[t.Any]:
|
) -> t.Optional[t.Any]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def get_default(
|
def get_default(
|
||||||
self, ctx: Context, call: bool = ...
|
self, ctx: Context, call: bool = ...
|
||||||
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
||||||
|
@ -2770,7 +2812,7 @@ class Option(Parameter):
|
||||||
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
||||||
# If we're a non boolean flag our default is more complex because
|
# If we're a non boolean flag our default is more complex because
|
||||||
# we need to look at all flags in the same group to figure out
|
# we need to look at all flags in the same group to figure out
|
||||||
# if we're the the default one in which case we return the flag
|
# if we're the default one in which case we return the flag
|
||||||
# value as default.
|
# value as default.
|
||||||
if self.is_flag and not self.is_bool_flag:
|
if self.is_flag and not self.is_bool_flag:
|
||||||
for param in ctx.command.params:
|
for param in ctx.command.params:
|
||||||
|
@ -2821,8 +2863,11 @@ class Option(Parameter):
|
||||||
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
|
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
|
||||||
rv = os.environ.get(envvar)
|
rv = os.environ.get(envvar)
|
||||||
|
|
||||||
|
if rv:
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]:
|
def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]:
|
||||||
rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx)
|
rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ from .globals import get_current_context
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
|
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
FC = t.TypeVar("FC", t.Callable[..., t.Any], Command)
|
FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command])
|
||||||
|
|
||||||
|
|
||||||
def pass_context(f: F) -> F:
|
def pass_context(f: F) -> F:
|
||||||
|
@ -121,43 +121,38 @@ def pass_meta_key(
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def _make_command(
|
CmdType = t.TypeVar("CmdType", bound=Command)
|
||||||
f: F,
|
|
||||||
name: t.Optional[str],
|
|
||||||
attrs: t.MutableMapping[str, t.Any],
|
@t.overload
|
||||||
cls: t.Type[Command],
|
def command(
|
||||||
|
__func: t.Callable[..., t.Any],
|
||||||
) -> Command:
|
) -> Command:
|
||||||
if isinstance(f, Command):
|
...
|
||||||
raise TypeError("Attempted to convert a callback into a command twice.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
params = f.__click_params__ # type: ignore
|
|
||||||
params.reverse()
|
|
||||||
del f.__click_params__ # type: ignore
|
|
||||||
except AttributeError:
|
|
||||||
params = []
|
|
||||||
|
|
||||||
help = attrs.get("help")
|
@t.overload
|
||||||
|
def command(
|
||||||
|
name: t.Optional[str] = None,
|
||||||
|
**attrs: t.Any,
|
||||||
|
) -> t.Callable[..., Command]:
|
||||||
|
...
|
||||||
|
|
||||||
if help is None:
|
|
||||||
help = inspect.getdoc(f)
|
|
||||||
else:
|
|
||||||
help = inspect.cleandoc(help)
|
|
||||||
|
|
||||||
attrs["help"] = help
|
@t.overload
|
||||||
return cls(
|
def command(
|
||||||
name=name or f.__name__.lower().replace("_", "-"),
|
name: t.Optional[str] = None,
|
||||||
callback=f,
|
cls: t.Type[CmdType] = ...,
|
||||||
params=params,
|
**attrs: t.Any,
|
||||||
**attrs,
|
) -> t.Callable[..., CmdType]:
|
||||||
)
|
...
|
||||||
|
|
||||||
|
|
||||||
def command(
|
def command(
|
||||||
name: t.Optional[str] = None,
|
name: t.Union[str, t.Callable[..., t.Any], None] = None,
|
||||||
cls: t.Optional[t.Type[Command]] = None,
|
cls: t.Optional[t.Type[Command]] = None,
|
||||||
**attrs: t.Any,
|
**attrs: t.Any,
|
||||||
) -> t.Callable[[F], Command]:
|
) -> t.Union[Command, t.Callable[..., Command]]:
|
||||||
r"""Creates a new :class:`Command` and uses the decorated function as
|
r"""Creates a new :class:`Command` and uses the decorated function as
|
||||||
callback. This will also automatically attach all decorated
|
callback. This will also automatically attach all decorated
|
||||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
||||||
|
@ -167,6 +162,8 @@ def command(
|
||||||
pass the intended name as the first argument.
|
pass the intended name as the first argument.
|
||||||
|
|
||||||
All keyword arguments are forwarded to the underlying command class.
|
All keyword arguments are forwarded to the underlying command class.
|
||||||
|
For the ``params`` argument, any decorated params are appended to
|
||||||
|
the end of the list.
|
||||||
|
|
||||||
Once decorated the function turns into a :class:`Command` instance
|
Once decorated the function turns into a :class:`Command` instance
|
||||||
that can be invoked as a command line utility or be attached to a
|
that can be invoked as a command line utility or be attached to a
|
||||||
|
@ -176,24 +173,91 @@ def command(
|
||||||
name with underscores replaced by dashes.
|
name with underscores replaced by dashes.
|
||||||
:param cls: the command class to instantiate. This defaults to
|
:param cls: the command class to instantiate. This defaults to
|
||||||
:class:`Command`.
|
:class:`Command`.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
This decorator can be applied without parentheses.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
The ``params`` argument can be used. Decorated params are
|
||||||
|
appended to the end of the list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
func: t.Optional[t.Callable[..., t.Any]] = None
|
||||||
|
|
||||||
|
if callable(name):
|
||||||
|
func = name
|
||||||
|
name = None
|
||||||
|
assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class."
|
||||||
|
assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments."
|
||||||
|
|
||||||
if cls is None:
|
if cls is None:
|
||||||
cls = Command
|
cls = Command
|
||||||
|
|
||||||
def decorator(f: t.Callable[..., t.Any]) -> Command:
|
def decorator(f: t.Callable[..., t.Any]) -> Command:
|
||||||
cmd = _make_command(f, name, attrs, cls) # type: ignore
|
if isinstance(f, Command):
|
||||||
|
raise TypeError("Attempted to convert a callback into a command twice.")
|
||||||
|
|
||||||
|
attr_params = attrs.pop("params", None)
|
||||||
|
params = attr_params if attr_params is not None else []
|
||||||
|
|
||||||
|
try:
|
||||||
|
decorator_params = f.__click_params__ # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
del f.__click_params__ # type: ignore
|
||||||
|
params.extend(reversed(decorator_params))
|
||||||
|
|
||||||
|
if attrs.get("help") is None:
|
||||||
|
attrs["help"] = f.__doc__
|
||||||
|
|
||||||
|
cmd = cls( # type: ignore[misc]
|
||||||
|
name=name or f.__name__.lower().replace("_", "-"), # type: ignore[arg-type]
|
||||||
|
callback=f,
|
||||||
|
params=params,
|
||||||
|
**attrs,
|
||||||
|
)
|
||||||
cmd.__doc__ = f.__doc__
|
cmd.__doc__ = f.__doc__
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
if func is not None:
|
||||||
|
return decorator(func)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]:
|
@t.overload
|
||||||
|
def group(
|
||||||
|
__func: t.Callable[..., t.Any],
|
||||||
|
) -> Group:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@t.overload
|
||||||
|
def group(
|
||||||
|
name: t.Optional[str] = None,
|
||||||
|
**attrs: t.Any,
|
||||||
|
) -> t.Callable[[F], Group]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def group(
|
||||||
|
name: t.Union[str, t.Callable[..., t.Any], None] = None, **attrs: t.Any
|
||||||
|
) -> t.Union[Group, t.Callable[[F], Group]]:
|
||||||
"""Creates a new :class:`Group` with a function as callback. This
|
"""Creates a new :class:`Group` with a function as callback. This
|
||||||
works otherwise the same as :func:`command` just that the `cls`
|
works otherwise the same as :func:`command` just that the `cls`
|
||||||
parameter is set to :class:`Group`.
|
parameter is set to :class:`Group`.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
This decorator can be applied without parentheses.
|
||||||
"""
|
"""
|
||||||
attrs.setdefault("cls", Group)
|
if attrs.get("cls") is None:
|
||||||
|
attrs["cls"] = Group
|
||||||
|
|
||||||
|
if callable(name):
|
||||||
|
grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs))
|
||||||
|
return grp(name)
|
||||||
|
|
||||||
return t.cast(Group, command(name, **attrs))
|
return t.cast(Group, command(name, **attrs))
|
||||||
|
|
||||||
|
|
||||||
|
@ -219,7 +283,7 @@ def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f: FC) -> FC:
|
def decorator(f: FC) -> FC:
|
||||||
ArgumentClass = attrs.pop("cls", Argument)
|
ArgumentClass = attrs.pop("cls", None) or Argument
|
||||||
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
@ -240,10 +304,7 @@ def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
def decorator(f: FC) -> FC:
|
def decorator(f: FC) -> FC:
|
||||||
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||||
option_attrs = attrs.copy()
|
option_attrs = attrs.copy()
|
||||||
|
OptionClass = option_attrs.pop("cls", None) or Option
|
||||||
if "help" in option_attrs:
|
|
||||||
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
|
|
||||||
OptionClass = option_attrs.pop("cls", Option)
|
|
||||||
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import typing
|
|
||||||
import typing as t
|
import typing as t
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
|
@ -9,12 +8,12 @@ if t.TYPE_CHECKING:
|
||||||
_local = local()
|
_local = local()
|
||||||
|
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
|
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@typing.overload
|
@t.overload
|
||||||
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
|
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
@ -102,10 +102,10 @@ _SOURCE_BASH = """\
|
||||||
IFS=',' read type value <<< "$completion"
|
IFS=',' read type value <<< "$completion"
|
||||||
|
|
||||||
if [[ $type == 'dir' ]]; then
|
if [[ $type == 'dir' ]]; then
|
||||||
COMREPLY=()
|
COMPREPLY=()
|
||||||
compopt -o dirnames
|
compopt -o dirnames
|
||||||
elif [[ $type == 'file' ]]; then
|
elif [[ $type == 'file' ]]; then
|
||||||
COMREPLY=()
|
COMPREPLY=()
|
||||||
compopt -o default
|
compopt -o default
|
||||||
elif [[ $type == 'plain' ]]; then
|
elif [[ $type == 'plain' ]]; then
|
||||||
COMPREPLY+=($value)
|
COMPREPLY+=($value)
|
||||||
|
@ -448,17 +448,16 @@ def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _start_of_option(value: str) -> bool:
|
def _start_of_option(ctx: Context, value: str) -> bool:
|
||||||
"""Check if the value looks like the start of an option."""
|
"""Check if the value looks like the start of an option."""
|
||||||
if not value:
|
if not value:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
c = value[0]
|
c = value[0]
|
||||||
# Allow "/" since that starts a path.
|
return c in ctx._opt_prefixes
|
||||||
return not c.isalnum() and c != "/"
|
|
||||||
|
|
||||||
|
|
||||||
def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
|
def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool:
|
||||||
"""Determine if the given parameter is an option that needs a value.
|
"""Determine if the given parameter is an option that needs a value.
|
||||||
|
|
||||||
:param args: List of complete args before the incomplete value.
|
:param args: List of complete args before the incomplete value.
|
||||||
|
@ -467,7 +466,7 @@ def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
|
||||||
if not isinstance(param, Option):
|
if not isinstance(param, Option):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if param.is_flag:
|
if param.is_flag or param.count:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
last_option = None
|
last_option = None
|
||||||
|
@ -476,7 +475,7 @@ def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
|
||||||
if index + 1 > param.nargs:
|
if index + 1 > param.nargs:
|
||||||
break
|
break
|
||||||
|
|
||||||
if _start_of_option(arg):
|
if _start_of_option(ctx, arg):
|
||||||
last_option = arg
|
last_option = arg
|
||||||
|
|
||||||
return last_option is not None and last_option in param.opts
|
return last_option is not None and last_option in param.opts
|
||||||
|
@ -551,7 +550,7 @@ def _resolve_incomplete(
|
||||||
# split and discard the "=" to make completion easier.
|
# split and discard the "=" to make completion easier.
|
||||||
if incomplete == "=":
|
if incomplete == "=":
|
||||||
incomplete = ""
|
incomplete = ""
|
||||||
elif "=" in incomplete and _start_of_option(incomplete):
|
elif "=" in incomplete and _start_of_option(ctx, incomplete):
|
||||||
name, _, incomplete = incomplete.partition("=")
|
name, _, incomplete = incomplete.partition("=")
|
||||||
args.append(name)
|
args.append(name)
|
||||||
|
|
||||||
|
@ -559,7 +558,7 @@ def _resolve_incomplete(
|
||||||
# even if they start with the option character. If it hasn't been
|
# even if they start with the option character. If it hasn't been
|
||||||
# given and the incomplete arg looks like an option, the current
|
# given and the incomplete arg looks like an option, the current
|
||||||
# command will provide option name completions.
|
# command will provide option name completions.
|
||||||
if "--" not in args and _start_of_option(incomplete):
|
if "--" not in args and _start_of_option(ctx, incomplete):
|
||||||
return ctx.command, incomplete
|
return ctx.command, incomplete
|
||||||
|
|
||||||
params = ctx.command.get_params(ctx)
|
params = ctx.command.get_params(ctx)
|
||||||
|
@ -567,7 +566,7 @@ def _resolve_incomplete(
|
||||||
# If the last complete arg is an option name with an incomplete
|
# If the last complete arg is an option name with an incomplete
|
||||||
# value, the option will provide value completions.
|
# value, the option will provide value completions.
|
||||||
for param in params:
|
for param in params:
|
||||||
if _is_incomplete_option(args, param):
|
if _is_incomplete_option(ctx, args, param):
|
||||||
return param, incomplete
|
return param, incomplete
|
||||||
|
|
||||||
# It's not an option name or value. The first argument without a
|
# It's not an option name or value. The first argument without a
|
||||||
|
|
|
@ -3,7 +3,6 @@ import io
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import typing
|
|
||||||
import typing as t
|
import typing as t
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
@ -94,7 +93,7 @@ def prompt(
|
||||||
"""Prompts a user for input. This is a convenience function that can
|
"""Prompts a user for input. This is a convenience function that can
|
||||||
be used to prompt a user for input later.
|
be used to prompt a user for input later.
|
||||||
|
|
||||||
If the user aborts the input by sending a interrupt signal, this
|
If the user aborts the input by sending an interrupt signal, this
|
||||||
function will catch it and raise a :exc:`Abort` exception.
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
:param text: the text to show for the prompt.
|
:param text: the text to show for the prompt.
|
||||||
|
@ -160,7 +159,6 @@ def prompt(
|
||||||
if confirmation_prompt is True:
|
if confirmation_prompt is True:
|
||||||
confirmation_prompt = _("Repeat for confirmation")
|
confirmation_prompt = _("Repeat for confirmation")
|
||||||
|
|
||||||
confirmation_prompt = t.cast(str, confirmation_prompt)
|
|
||||||
confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
|
confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -182,9 +180,9 @@ def prompt(
|
||||||
if not confirmation_prompt:
|
if not confirmation_prompt:
|
||||||
return result
|
return result
|
||||||
while True:
|
while True:
|
||||||
confirmation_prompt = t.cast(str, confirmation_prompt)
|
|
||||||
value2 = prompt_func(confirmation_prompt)
|
value2 = prompt_func(confirmation_prompt)
|
||||||
if value2:
|
is_empty = not value and not value2
|
||||||
|
if value2 or is_empty:
|
||||||
break
|
break
|
||||||
if value == value2:
|
if value == value2:
|
||||||
return result
|
return result
|
||||||
|
@ -252,26 +250,6 @@ def confirm(
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def get_terminal_size() -> os.terminal_size:
|
|
||||||
"""Returns the current size of the terminal as tuple in the form
|
|
||||||
``(width, height)`` in columns and rows.
|
|
||||||
|
|
||||||
.. deprecated:: 8.0
|
|
||||||
Will be removed in Click 8.1. Use
|
|
||||||
:func:`shutil.get_terminal_size` instead.
|
|
||||||
"""
|
|
||||||
import shutil
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'click.get_terminal_size()' is deprecated and will be removed"
|
|
||||||
" in Click 8.1. Use 'shutil.get_terminal_size()' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return shutil.get_terminal_size()
|
|
||||||
|
|
||||||
|
|
||||||
def echo_via_pager(
|
def echo_via_pager(
|
||||||
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
|
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
|
||||||
color: t.Optional[bool] = None,
|
color: t.Optional[bool] = None,
|
||||||
|
@ -627,7 +605,7 @@ def unstyle(text: str) -> str:
|
||||||
|
|
||||||
def secho(
|
def secho(
|
||||||
message: t.Optional[t.Any] = None,
|
message: t.Optional[t.Any] = None,
|
||||||
file: t.Optional[t.IO] = None,
|
file: t.Optional[t.IO[t.AnyStr]] = None,
|
||||||
nl: bool = True,
|
nl: bool = True,
|
||||||
err: bool = False,
|
err: bool = False,
|
||||||
color: t.Optional[bool] = None,
|
color: t.Optional[bool] = None,
|
||||||
|
|
|
@ -464,16 +464,16 @@ class CliRunner:
|
||||||
Added the ``temp_dir`` parameter.
|
Added the ``temp_dir`` parameter.
|
||||||
"""
|
"""
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
t = tempfile.mkdtemp(dir=temp_dir)
|
dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var]
|
||||||
os.chdir(t)
|
os.chdir(dt)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield t
|
yield t.cast(str, dt)
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
if temp_dir is None:
|
if temp_dir is None:
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(t)
|
shutil.rmtree(dt)
|
||||||
except OSError: # noqa: B014
|
except OSError: # noqa: B014
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -63,7 +63,14 @@ class ParamType:
|
||||||
# The class name without the "ParamType" suffix.
|
# The class name without the "ParamType" suffix.
|
||||||
param_type = type(self).__name__.partition("ParamType")[0]
|
param_type = type(self).__name__.partition("ParamType")[0]
|
||||||
param_type = param_type.partition("ParameterType")[0]
|
param_type = param_type.partition("ParameterType")[0]
|
||||||
return {"param_type": param_type, "name": self.name}
|
|
||||||
|
# Custom subclasses might not remember to set a name.
|
||||||
|
if hasattr(self, "name"):
|
||||||
|
name = self.name
|
||||||
|
else:
|
||||||
|
name = param_type
|
||||||
|
|
||||||
|
return {"param_type": param_type, "name": name}
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self,
|
self,
|
||||||
|
@ -724,7 +731,7 @@ class File(ParamType):
|
||||||
|
|
||||||
return f
|
return f
|
||||||
except OSError as e: # noqa: B014
|
except OSError as e: # noqa: B014
|
||||||
self.fail(f"{os.fsdecode(value)!r}: {e.strerror}", param, ctx)
|
self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx)
|
||||||
|
|
||||||
def shell_complete(
|
def shell_complete(
|
||||||
self, ctx: "Context", param: "Parameter", incomplete: str
|
self, ctx: "Context", param: "Parameter", incomplete: str
|
||||||
|
@ -744,30 +751,31 @@ class File(ParamType):
|
||||||
|
|
||||||
|
|
||||||
class Path(ParamType):
|
class Path(ParamType):
|
||||||
"""The path type is similar to the :class:`File` type but it performs
|
"""The ``Path`` type is similar to the :class:`File` type, but
|
||||||
different checks. First of all, instead of returning an open file
|
returns the filename instead of an open file. Various checks can be
|
||||||
handle it returns just the filename. Secondly, it can perform various
|
enabled to validate the type of file and permissions.
|
||||||
basic checks about what the file or directory should be.
|
|
||||||
|
|
||||||
:param exists: if set to true, the file or directory needs to exist for
|
:param exists: The file or directory needs to exist for the value to
|
||||||
this value to be valid. If this is not required and a
|
be valid. If this is not set to ``True``, and the file does not
|
||||||
file does indeed not exist, then all further checks are
|
exist, then all further checks are silently skipped.
|
||||||
silently skipped.
|
:param file_okay: Allow a file as a value.
|
||||||
:param file_okay: controls if a file is a possible value.
|
:param dir_okay: Allow a directory as a value.
|
||||||
:param dir_okay: controls if a directory is a possible value.
|
|
||||||
:param writable: if true, a writable check is performed.
|
|
||||||
:param readable: if true, a readable check is performed.
|
:param readable: if true, a readable check is performed.
|
||||||
:param resolve_path: if this is true, then the path is fully resolved
|
:param writable: if true, a writable check is performed.
|
||||||
before the value is passed onwards. This means
|
:param executable: if true, an executable check is performed.
|
||||||
that it's absolute and symlinks are resolved. It
|
:param resolve_path: Make the value absolute and resolve any
|
||||||
will not expand a tilde-prefix, as this is
|
symlinks. A ``~`` is not expanded, as this is supposed to be
|
||||||
supposed to be done by the shell only.
|
done by the shell only.
|
||||||
:param allow_dash: If this is set to `True`, a single dash to indicate
|
:param allow_dash: Allow a single dash as a value, which indicates
|
||||||
standard streams is permitted.
|
a standard stream (but does not open it). Use
|
||||||
|
:func:`~click.open_file` to handle opening this value.
|
||||||
:param path_type: Convert the incoming path value to this type. If
|
:param path_type: Convert the incoming path value to this type. If
|
||||||
``None``, keep Python's default, which is ``str``. Useful to
|
``None``, keep Python's default, which is ``str``. Useful to
|
||||||
convert to :class:`pathlib.Path`.
|
convert to :class:`pathlib.Path`.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
Added the ``executable`` parameter.
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
.. versionchanged:: 8.0
|
||||||
Allow passing ``type=pathlib.Path``.
|
Allow passing ``type=pathlib.Path``.
|
||||||
|
|
||||||
|
@ -787,12 +795,14 @@ class Path(ParamType):
|
||||||
resolve_path: bool = False,
|
resolve_path: bool = False,
|
||||||
allow_dash: bool = False,
|
allow_dash: bool = False,
|
||||||
path_type: t.Optional[t.Type] = None,
|
path_type: t.Optional[t.Type] = None,
|
||||||
|
executable: bool = False,
|
||||||
):
|
):
|
||||||
self.exists = exists
|
self.exists = exists
|
||||||
self.file_okay = file_okay
|
self.file_okay = file_okay
|
||||||
self.dir_okay = dir_okay
|
self.dir_okay = dir_okay
|
||||||
self.writable = writable
|
|
||||||
self.readable = readable
|
self.readable = readable
|
||||||
|
self.writable = writable
|
||||||
|
self.executable = executable
|
||||||
self.resolve_path = resolve_path
|
self.resolve_path = resolve_path
|
||||||
self.allow_dash = allow_dash
|
self.allow_dash = allow_dash
|
||||||
self.type = path_type
|
self.type = path_type
|
||||||
|
@ -865,12 +875,22 @@ class Path(ParamType):
|
||||||
)
|
)
|
||||||
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
||||||
self.fail(
|
self.fail(
|
||||||
_("{name} {filename!r} is a directory.").format(
|
_("{name} '{filename}' is a directory.").format(
|
||||||
name=self.name.title(), filename=os.fsdecode(value)
|
name=self.name.title(), filename=os.fsdecode(value)
|
||||||
),
|
),
|
||||||
param,
|
param,
|
||||||
ctx,
|
ctx,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.readable and not os.access(rv, os.R_OK):
|
||||||
|
self.fail(
|
||||||
|
_("{name} {filename!r} is not readable.").format(
|
||||||
|
name=self.name.title(), filename=os.fsdecode(value)
|
||||||
|
),
|
||||||
|
param,
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
|
||||||
if self.writable and not os.access(rv, os.W_OK):
|
if self.writable and not os.access(rv, os.W_OK):
|
||||||
self.fail(
|
self.fail(
|
||||||
_("{name} {filename!r} is not writable.").format(
|
_("{name} {filename!r} is not writable.").format(
|
||||||
|
@ -879,9 +899,10 @@ class Path(ParamType):
|
||||||
param,
|
param,
|
||||||
ctx,
|
ctx,
|
||||||
)
|
)
|
||||||
if self.readable and not os.access(rv, os.R_OK):
|
|
||||||
|
if self.executable and not os.access(value, os.X_OK):
|
||||||
self.fail(
|
self.fail(
|
||||||
_("{name} {filename!r} is not readable.").format(
|
_("{name} {filename!r} is not executable.").format(
|
||||||
name=self.name.title(), filename=os.fsdecode(value)
|
name=self.name.title(), filename=os.fsdecode(value)
|
||||||
),
|
),
|
||||||
param,
|
param,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
@ -203,7 +204,7 @@ class KeepOpenFile:
|
||||||
|
|
||||||
def echo(
|
def echo(
|
||||||
message: t.Optional[t.Any] = None,
|
message: t.Optional[t.Any] = None,
|
||||||
file: t.Optional[t.IO] = None,
|
file: t.Optional[t.IO[t.Any]] = None,
|
||||||
nl: bool = True,
|
nl: bool = True,
|
||||||
err: bool = False,
|
err: bool = False,
|
||||||
color: t.Optional[bool] = None,
|
color: t.Optional[bool] = None,
|
||||||
|
@ -340,55 +341,45 @@ def open_file(
|
||||||
lazy: bool = False,
|
lazy: bool = False,
|
||||||
atomic: bool = False,
|
atomic: bool = False,
|
||||||
) -> t.IO:
|
) -> t.IO:
|
||||||
"""This is similar to how the :class:`File` works but for manual
|
"""Open a file, with extra behavior to handle ``'-'`` to indicate
|
||||||
usage. Files are opened non lazy by default. This can open regular
|
a standard stream, lazy open on write, and atomic write. Similar to
|
||||||
files as well as stdin/stdout if ``'-'`` is passed.
|
the behavior of the :class:`~click.File` param type.
|
||||||
|
|
||||||
If stdin/stdout is returned the stream is wrapped so that the context
|
If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
|
||||||
manager will not close the stream accidentally. This makes it possible
|
wrapped so that using it in a context manager will not close it.
|
||||||
to always use the function like this without having to worry to
|
This makes it possible to use the function without accidentally
|
||||||
accidentally close a standard stream::
|
closing a standard stream:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
with open_file(filename) as f:
|
with open_file(filename) as f:
|
||||||
...
|
...
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
:param filename: The name of the file to open, or ``'-'`` for
|
||||||
|
``stdin``/``stdout``.
|
||||||
|
:param mode: The mode in which to open the file.
|
||||||
|
:param encoding: The encoding to decode or encode a file opened in
|
||||||
|
text mode.
|
||||||
|
:param errors: The error handling mode.
|
||||||
|
:param lazy: Wait to open the file until it is accessed. For read
|
||||||
|
mode, the file is temporarily opened to raise access errors
|
||||||
|
early, then closed until it is read again.
|
||||||
|
:param atomic: Write to a temporary file and replace the given file
|
||||||
|
on close.
|
||||||
|
|
||||||
:param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
|
.. versionadded:: 3.0
|
||||||
:param mode: the mode in which to open the file.
|
|
||||||
:param encoding: the encoding to use.
|
|
||||||
:param errors: the error handling for this file.
|
|
||||||
:param lazy: can be flipped to true to open the file lazily.
|
|
||||||
:param atomic: in atomic mode writes go into a temporary file and it's
|
|
||||||
moved on close.
|
|
||||||
"""
|
"""
|
||||||
if lazy:
|
if lazy:
|
||||||
return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic))
|
return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic))
|
||||||
|
|
||||||
f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
|
f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
|
||||||
|
|
||||||
if not should_close:
|
if not should_close:
|
||||||
f = t.cast(t.IO, KeepOpenFile(f))
|
f = t.cast(t.IO, KeepOpenFile(f))
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def get_os_args() -> t.Sequence[str]:
|
|
||||||
"""Returns the argument part of ``sys.argv``, removing the first
|
|
||||||
value which is the name of the script.
|
|
||||||
|
|
||||||
.. deprecated:: 8.0
|
|
||||||
Will be removed in Click 8.1. Access ``sys.argv[1:]`` directly
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'get_os_args' is deprecated and will be removed in Click 8.1."
|
|
||||||
" Access 'sys.argv[1:]' directly instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return sys.argv[1:]
|
|
||||||
|
|
||||||
|
|
||||||
def format_filename(
|
def format_filename(
|
||||||
filename: t.Union[str, bytes, os.PathLike], shorten: bool = False
|
filename: t.Union[str, bytes, os.PathLike], shorten: bool = False
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -484,7 +475,7 @@ class PacifyFlushWrapper:
|
||||||
|
|
||||||
|
|
||||||
def _detect_program_name(
|
def _detect_program_name(
|
||||||
path: t.Optional[str] = None, _main: ModuleType = sys.modules["__main__"]
|
path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Determine the command used to run the program, for use in help
|
"""Determine the command used to run the program, for use in help
|
||||||
text. If a file or entry point was executed, the file name is
|
text. If a file or entry point was executed, the file name is
|
||||||
|
@ -506,6 +497,9 @@ def _detect_program_name(
|
||||||
|
|
||||||
:meta private:
|
:meta private:
|
||||||
"""
|
"""
|
||||||
|
if _main is None:
|
||||||
|
_main = sys.modules["__main__"]
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
path = sys.argv[0]
|
path = sys.argv[0]
|
||||||
|
|
||||||
|
@ -546,7 +540,7 @@ def _expand_args(
|
||||||
See :func:`glob.glob`, :func:`os.path.expanduser`, and
|
See :func:`glob.glob`, :func:`os.path.expanduser`, and
|
||||||
:func:`os.path.expandvars`.
|
:func:`os.path.expandvars`.
|
||||||
|
|
||||||
This intended for use on Windows, where the shell does not do any
|
This is intended for use on Windows, where the shell does not do any
|
||||||
expansion. It may not exactly match what a Unix shell would do.
|
expansion. It may not exactly match what a Unix shell would do.
|
||||||
|
|
||||||
:param args: List of command line arguments to expand.
|
:param args: List of command line arguments to expand.
|
||||||
|
@ -554,6 +548,10 @@ def _expand_args(
|
||||||
:param env: Expand environment variables.
|
:param env: Expand environment variables.
|
||||||
:param glob_recursive: ``**`` matches directories recursively.
|
:param glob_recursive: ``**`` matches directories recursively.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.1
|
||||||
|
Invalid glob patterns are treated as empty expansions rather
|
||||||
|
than raising an error.
|
||||||
|
|
||||||
.. versionadded:: 8.0
|
.. versionadded:: 8.0
|
||||||
|
|
||||||
:meta private:
|
:meta private:
|
||||||
|
@ -569,7 +567,10 @@ def _expand_args(
|
||||||
if env:
|
if env:
|
||||||
arg = os.path.expandvars(arg)
|
arg = os.path.expandvars(arg)
|
||||||
|
|
||||||
|
try:
|
||||||
matches = glob(arg, recursive=glob_recursive)
|
matches = glob(arg, recursive=glob_recursive)
|
||||||
|
except re.error:
|
||||||
|
matches = []
|
||||||
|
|
||||||
if not matches:
|
if not matches:
|
||||||
out.append(arg)
|
out.append(arg)
|
||||||
|
|
|
@ -41,16 +41,16 @@ def test_nargs_tup(runner):
|
||||||
assert result.output.splitlines() == ["name=peter", "point=1/2"]
|
assert result.output.splitlines() == ["name=peter", "point=1/2"]
|
||||||
|
|
||||||
|
|
||||||
def test_nargs_tup_composite(runner):
|
@pytest.mark.parametrize(
|
||||||
variations = [
|
"opts",
|
||||||
|
[
|
||||||
dict(type=(str, int)),
|
dict(type=(str, int)),
|
||||||
dict(type=click.Tuple([str, int])),
|
dict(type=click.Tuple([str, int])),
|
||||||
dict(nargs=2, type=click.Tuple([str, int])),
|
dict(nargs=2, type=click.Tuple([str, int])),
|
||||||
dict(nargs=2, type=(str, int)),
|
dict(nargs=2, type=(str, int)),
|
||||||
]
|
],
|
||||||
|
)
|
||||||
for opts in variations:
|
def test_nargs_tup_composite(runner, opts):
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("item", **opts)
|
@click.argument("item", **opts)
|
||||||
def copy(item):
|
def copy(item):
|
||||||
|
@ -58,7 +58,7 @@ def test_nargs_tup_composite(runner):
|
||||||
click.echo(f"name={name} id={id:d}")
|
click.echo(f"name={name} id={id:d}")
|
||||||
|
|
||||||
result = runner.invoke(copy, ["peter", "1"])
|
result = runner.invoke(copy, ["peter", "1"])
|
||||||
assert not result.exception
|
assert result.exception is None
|
||||||
assert result.output.splitlines() == ["name=peter id=1"]
|
assert result.output.splitlines() == ["name=peter id=1"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,9 +120,9 @@ def test_file_args(runner):
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
|
||||||
def test_path_args(runner):
|
def test_path_allow_dash(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("input", type=click.Path(dir_okay=False, allow_dash=True))
|
@click.argument("input", type=click.Path(allow_dash=True))
|
||||||
def foo(input):
|
def foo(input):
|
||||||
click.echo(input)
|
click.echo(input)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -104,125 +103,135 @@ def test_group_from_list(runner):
|
||||||
assert result.output == "sub"
|
assert result.output == "sub"
|
||||||
|
|
||||||
|
|
||||||
def test_basic_option(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expect"),
|
||||||
|
[
|
||||||
|
([], "S:[no value]"),
|
||||||
|
(["--s=42"], "S:[42]"),
|
||||||
|
(["--s"], "Error: Option '--s' requires an argument."),
|
||||||
|
(["--s="], "S:[]"),
|
||||||
|
(["--s=\N{SNOWMAN}"], "S:[\N{SNOWMAN}]"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_string_option(runner, args, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--foo", default="no value")
|
@click.option("--s", default="no value")
|
||||||
def cli(foo):
|
def cli(s):
|
||||||
click.echo(f"FOO:[{foo}]")
|
click.echo(f"S:[{s}]")
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, args)
|
||||||
assert not result.exception
|
assert expect in result.output
|
||||||
assert "FOO:[no value]" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--foo=42"])
|
if expect.startswith("Error:"):
|
||||||
assert not result.exception
|
assert result.exception is not None
|
||||||
assert "FOO:[42]" in result.output
|
else:
|
||||||
|
assert result.exception is None
|
||||||
result = runner.invoke(cli, ["--foo"])
|
|
||||||
assert result.exception
|
|
||||||
assert "Option '--foo' requires an argument." in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--foo="])
|
|
||||||
assert not result.exception
|
|
||||||
assert "FOO:[]" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--foo=\N{SNOWMAN}"])
|
|
||||||
assert not result.exception
|
|
||||||
assert "FOO:[\N{SNOWMAN}]" in result.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_int_option(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expect"),
|
||||||
|
[
|
||||||
|
([], "I:[84]"),
|
||||||
|
(["--i=23"], "I:[46]"),
|
||||||
|
(["--i=x"], "Error: Invalid value for '--i': 'x' is not a valid integer."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_int_option(runner, args, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--foo", default=42)
|
@click.option("--i", default=42)
|
||||||
def cli(foo):
|
def cli(i):
|
||||||
click.echo(f"FOO:[{foo * 2}]")
|
click.echo(f"I:[{i * 2}]")
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, args)
|
||||||
assert not result.exception
|
assert expect in result.output
|
||||||
assert "FOO:[84]" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--foo=23"])
|
if expect.startswith("Error:"):
|
||||||
assert not result.exception
|
assert result.exception is not None
|
||||||
assert "FOO:[46]" in result.output
|
else:
|
||||||
|
assert result.exception is None
|
||||||
result = runner.invoke(cli, ["--foo=bar"])
|
|
||||||
assert result.exception
|
|
||||||
assert "Invalid value for '--foo': 'bar' is not a valid integer." in result.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_uuid_option(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expect"),
|
||||||
|
[
|
||||||
|
([], "U:[ba122011-349f-423b-873b-9d6a79c688ab]"),
|
||||||
|
(
|
||||||
|
["--u=821592c1-c50e-4971-9cd6-e89dc6832f86"],
|
||||||
|
"U:[821592c1-c50e-4971-9cd6-e89dc6832f86]",
|
||||||
|
),
|
||||||
|
(["--u=x"], "Error: Invalid value for '--u': 'x' is not a valid UUID."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_uuid_option(runner, args, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"--u", default="ba122011-349f-423b-873b-9d6a79c688ab", type=click.UUID
|
"--u", default="ba122011-349f-423b-873b-9d6a79c688ab", type=click.UUID
|
||||||
)
|
)
|
||||||
def cli(u):
|
def cli(u):
|
||||||
assert type(u) is uuid.UUID
|
|
||||||
click.echo(f"U:[{u}]")
|
click.echo(f"U:[{u}]")
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, args)
|
||||||
assert not result.exception
|
assert expect in result.output
|
||||||
assert "U:[ba122011-349f-423b-873b-9d6a79c688ab]" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--u=821592c1-c50e-4971-9cd6-e89dc6832f86"])
|
if expect.startswith("Error:"):
|
||||||
assert not result.exception
|
assert result.exception is not None
|
||||||
assert "U:[821592c1-c50e-4971-9cd6-e89dc6832f86]" in result.output
|
else:
|
||||||
|
assert result.exception is None
|
||||||
result = runner.invoke(cli, ["--u=bar"])
|
|
||||||
assert result.exception
|
|
||||||
assert "Invalid value for '--u': 'bar' is not a valid UUID." in result.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_float_option(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expect"),
|
||||||
|
[
|
||||||
|
([], "F:[42.0]"),
|
||||||
|
("--f=23.5", "F:[23.5]"),
|
||||||
|
("--f=x", "Error: Invalid value for '--f': 'x' is not a valid float."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_float_option(runner, args, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--foo", default=42, type=click.FLOAT)
|
@click.option("--f", default=42.0)
|
||||||
def cli(foo):
|
def cli(f):
|
||||||
assert type(foo) is float
|
click.echo(f"F:[{f}]")
|
||||||
click.echo(f"FOO:[{foo}]")
|
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, args)
|
||||||
assert not result.exception
|
assert expect in result.output
|
||||||
assert "FOO:[42.0]" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--foo=23.5"])
|
if expect.startswith("Error:"):
|
||||||
assert not result.exception
|
assert result.exception is not None
|
||||||
assert "FOO:[23.5]" in result.output
|
else:
|
||||||
|
assert result.exception is None
|
||||||
result = runner.invoke(cli, ["--foo=bar"])
|
|
||||||
assert result.exception
|
|
||||||
assert "Invalid value for '--foo': 'bar' is not a valid float." in result.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_boolean_option(runner):
|
@pytest.mark.parametrize("default", [True, False])
|
||||||
for default in True, False:
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expect"), [(["--on"], True), (["--off"], False), ([], None)]
|
||||||
|
)
|
||||||
|
def test_boolean_switch(runner, default, args, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--with-foo/--without-foo", default=default)
|
@click.option("--on/--off", default=default)
|
||||||
def cli(with_foo):
|
def cli(on):
|
||||||
click.echo(with_foo)
|
return on
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--with-foo"])
|
if expect is None:
|
||||||
assert not result.exception
|
expect = default
|
||||||
assert result.output == "True\n"
|
|
||||||
result = runner.invoke(cli, ["--without-foo"])
|
|
||||||
assert not result.exception
|
|
||||||
assert result.output == "False\n"
|
|
||||||
result = runner.invoke(cli, [])
|
|
||||||
assert not result.exception
|
|
||||||
assert result.output == f"{default}\n"
|
|
||||||
|
|
||||||
for default in True, False:
|
result = runner.invoke(cli, args, standalone_mode=False)
|
||||||
|
assert result.return_value is expect
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("default", [True, False])
|
||||||
|
@pytest.mark.parametrize(("args", "expect"), [(["--f"], True), ([], False)])
|
||||||
|
def test_boolean_flag(runner, default, args, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--flag", is_flag=True, default=default)
|
@click.option("--f", is_flag=True, default=default)
|
||||||
def cli(flag):
|
def cli(f):
|
||||||
click.echo(flag)
|
return f
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--flag"])
|
if default:
|
||||||
assert not result.exception
|
expect = not expect
|
||||||
assert result.output == f"{not default}\n"
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, args, standalone_mode=False)
|
||||||
assert not result.exception
|
assert result.return_value is expect
|
||||||
assert result.output == f"{default}\n"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -34,7 +34,17 @@ def test_basic_chaining(runner):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_chaining_help(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expect"),
|
||||||
|
[
|
||||||
|
(["--help"], "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..."),
|
||||||
|
(["--help"], "ROOT HELP"),
|
||||||
|
(["sdist", "--help"], "SDIST HELP"),
|
||||||
|
(["bdist", "--help"], "BDIST HELP"),
|
||||||
|
(["bdist", "sdist", "--help"], "SDIST HELP"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_chaining_help(runner, args, expect):
|
||||||
@click.group(chain=True)
|
@click.group(chain=True)
|
||||||
def cli():
|
def cli():
|
||||||
"""ROOT HELP"""
|
"""ROOT HELP"""
|
||||||
|
@ -50,22 +60,9 @@ def test_chaining_help(runner):
|
||||||
"""BDIST HELP"""
|
"""BDIST HELP"""
|
||||||
click.echo("bdist called")
|
click.echo("bdist called")
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--help"])
|
result = runner.invoke(cli, args)
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." in result.output
|
assert expect in result.output
|
||||||
assert "ROOT HELP" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["sdist", "--help"])
|
|
||||||
assert not result.exception
|
|
||||||
assert "SDIST HELP" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["bdist", "--help"])
|
|
||||||
assert not result.exception
|
|
||||||
assert "BDIST HELP" in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["bdist", "sdist", "--help"])
|
|
||||||
assert not result.exception
|
|
||||||
assert "SDIST HELP" in result.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_chaining_with_options(runner):
|
def test_chaining_with_options(runner):
|
||||||
|
@ -88,20 +85,20 @@ def test_chaining_with_options(runner):
|
||||||
assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
|
assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(("chain", "expect"), [(False, "None"), (True, "[]")])
|
@pytest.mark.parametrize(("chain", "expect"), [(False, "1"), (True, "[]")])
|
||||||
def test_no_command_result_callback(runner, chain, expect):
|
def test_no_command_result_callback(runner, chain, expect):
|
||||||
"""When a group has ``invoke_without_command=True``, the result
|
"""When a group has ``invoke_without_command=True``, the result
|
||||||
callback is always invoked. A regular group invokes it with
|
callback is always invoked. A regular group invokes it with
|
||||||
``None``, a chained group with ``[]``.
|
its return value, a chained group with ``[]``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@click.group(invoke_without_command=True, chain=chain)
|
@click.group(invoke_without_command=True, chain=chain)
|
||||||
def cli():
|
def cli():
|
||||||
pass
|
return 1
|
||||||
|
|
||||||
@cli.result_callback()
|
@cli.result_callback()
|
||||||
def process_result(result):
|
def process_result(result):
|
||||||
click.echo(str(result), nl=False)
|
click.echo(result, nl=False)
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, [])
|
||||||
assert result.output == expect
|
assert result.output == expect
|
||||||
|
@ -127,15 +124,23 @@ def test_chaining_with_arguments(runner):
|
||||||
assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
|
assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
|
||||||
|
|
||||||
|
|
||||||
def test_pipeline(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "input", "expect"),
|
||||||
|
[
|
||||||
|
(["-f", "-"], "foo\nbar", ["foo", "bar"]),
|
||||||
|
(["-f", "-", "strip"], "foo \n bar", ["foo", "bar"]),
|
||||||
|
(["-f", "-", "strip", "uppercase"], "foo \n bar", ["FOO", "BAR"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_pipeline(runner, args, input, expect):
|
||||||
@click.group(chain=True, invoke_without_command=True)
|
@click.group(chain=True, invoke_without_command=True)
|
||||||
@click.option("-i", "--input", type=click.File("r"))
|
@click.option("-f", type=click.File("r"))
|
||||||
def cli(input):
|
def cli(f):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@cli.result_callback()
|
@cli.result_callback()
|
||||||
def process_pipeline(processors, input):
|
def process_pipeline(processors, f):
|
||||||
iterator = (x.rstrip("\r\n") for x in input)
|
iterator = (x.rstrip("\r\n") for x in f)
|
||||||
for processor in processors:
|
for processor in processors:
|
||||||
iterator = processor(iterator)
|
iterator = processor(iterator)
|
||||||
for item in iterator:
|
for item in iterator:
|
||||||
|
@ -157,17 +162,9 @@ def test_pipeline(runner):
|
||||||
|
|
||||||
return processor
|
return processor
|
||||||
|
|
||||||
result = runner.invoke(cli, ["-i", "-"], input="foo\nbar")
|
result = runner.invoke(cli, args, input=input)
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert result.output.splitlines() == ["foo", "bar"]
|
assert result.output.splitlines() == expect
|
||||||
|
|
||||||
result = runner.invoke(cli, ["-i", "-", "strip"], input="foo \n bar")
|
|
||||||
assert not result.exception
|
|
||||||
assert result.output.splitlines() == ["foo", "bar"]
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["-i", "-", "strip", "uppercase"], input="foo \n bar")
|
|
||||||
assert not result.exception
|
|
||||||
assert result.output.splitlines() == ["FOO", "BAR"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_args_and_chain(runner):
|
def test_args_and_chain(runner):
|
||||||
|
|
51
tests/test_command_decorators.py
Normal file
51
tests/test_command_decorators.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
|
def test_command_no_parens(runner):
|
||||||
|
@click.command
|
||||||
|
def cli():
|
||||||
|
click.echo("hello")
|
||||||
|
|
||||||
|
result = runner.invoke(cli)
|
||||||
|
assert result.exception is None
|
||||||
|
assert result.output == "hello\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_no_parens(runner):
|
||||||
|
@click.group
|
||||||
|
def grp():
|
||||||
|
click.echo("grp1")
|
||||||
|
|
||||||
|
@grp.command
|
||||||
|
def cmd1():
|
||||||
|
click.echo("cmd1")
|
||||||
|
|
||||||
|
@grp.group
|
||||||
|
def grp2():
|
||||||
|
click.echo("grp2")
|
||||||
|
|
||||||
|
@grp2.command
|
||||||
|
def cmd2():
|
||||||
|
click.echo("cmd2")
|
||||||
|
|
||||||
|
result = runner.invoke(grp, ["cmd1"])
|
||||||
|
assert result.exception is None
|
||||||
|
assert result.output == "grp1\ncmd1\n"
|
||||||
|
|
||||||
|
result = runner.invoke(grp, ["grp2", "cmd2"])
|
||||||
|
assert result.exception is None
|
||||||
|
assert result.output == "grp1\ngrp2\ncmd2\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_params_argument(runner):
|
||||||
|
opt = click.Argument(["a"])
|
||||||
|
|
||||||
|
@click.command(params=[opt])
|
||||||
|
@click.argument("b")
|
||||||
|
def cli(a, b):
|
||||||
|
click.echo(f"{a} {b}")
|
||||||
|
|
||||||
|
assert cli.params[0].name == "a"
|
||||||
|
assert cli.params[1].name == "b"
|
||||||
|
result = runner.invoke(cli, ["1", "2"])
|
||||||
|
assert result.output == "1 2\n"
|
|
@ -1,5 +1,7 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,6 +95,20 @@ def test_auto_shorthelp(runner):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_help_truncation(runner):
|
||||||
|
@click.command()
|
||||||
|
def cli():
|
||||||
|
"""This is a command with truncated help.
|
||||||
|
\f
|
||||||
|
|
||||||
|
This text should be truncated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ["--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "This is a command with truncated help." in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_no_args_is_help(runner):
|
def test_no_args_is_help(runner):
|
||||||
@click.command(no_args_is_help=True)
|
@click.command(no_args_is_help=True)
|
||||||
def cli():
|
def cli():
|
||||||
|
@ -119,7 +135,16 @@ def test_default_maps(runner):
|
||||||
assert result.output == "changed\n"
|
assert result.output == "changed\n"
|
||||||
|
|
||||||
|
|
||||||
def test_group_with_args(runner):
|
@pytest.mark.parametrize(
|
||||||
|
("args", "exit_code", "expect"),
|
||||||
|
[
|
||||||
|
(["obj1"], 2, "Error: Missing command."),
|
||||||
|
(["obj1", "--help"], 0, "Show this message and exit."),
|
||||||
|
(["obj1", "move"], 0, "obj=obj1\nmove\n"),
|
||||||
|
([], 0, "Show this message and exit."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_group_with_args(runner, args, exit_code, expect):
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.argument("obj")
|
@click.argument("obj")
|
||||||
def cli(obj):
|
def cli(obj):
|
||||||
|
@ -129,21 +154,9 @@ def test_group_with_args(runner):
|
||||||
def move():
|
def move():
|
||||||
click.echo("move")
|
click.echo("move")
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, args)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == exit_code
|
||||||
assert "Show this message and exit." in result.output
|
assert expect in result.output
|
||||||
|
|
||||||
result = runner.invoke(cli, ["obj1"])
|
|
||||||
assert result.exit_code == 2
|
|
||||||
assert "Error: Missing command." in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["obj1", "--help"])
|
|
||||||
assert result.exit_code == 0
|
|
||||||
assert "Show this message and exit." in result.output
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["obj1", "move"])
|
|
||||||
assert result.exit_code == 0
|
|
||||||
assert result.output == "obj=obj1\nmove\n"
|
|
||||||
|
|
||||||
|
|
||||||
def test_base_command(runner):
|
def test_base_command(runner):
|
||||||
|
@ -196,18 +209,12 @@ def test_base_command(runner):
|
||||||
|
|
||||||
cli.add_command(OptParseCommand("test", parser, test_callback))
|
cli.add_command(OptParseCommand("test", parser, test_callback))
|
||||||
|
|
||||||
result = runner.invoke(
|
result = runner.invoke(cli, ["test", "-f", "f.txt", "-q", "q1.txt", "q2.txt"])
|
||||||
cli, ["test", "-f", "test.txt", "-q", "whatever.txt", "whateverelse.txt"]
|
assert result.exception is None
|
||||||
)
|
assert result.output.splitlines() == ["q1.txt q2.txt", "f.txt", "False"]
|
||||||
assert not result.exception
|
|
||||||
assert result.output.splitlines() == [
|
|
||||||
"whatever.txt whateverelse.txt",
|
|
||||||
"test.txt",
|
|
||||||
"False",
|
|
||||||
]
|
|
||||||
|
|
||||||
result = runner.invoke(cli, ["test", "--help"])
|
result = runner.invoke(cli, ["test", "--help"])
|
||||||
assert not result.exception
|
assert result.exception is None
|
||||||
assert result.output.splitlines() == [
|
assert result.output.splitlines() == [
|
||||||
"Usage: foo test [OPTIONS]",
|
"Usage: foo test [OPTIONS]",
|
||||||
"",
|
"",
|
||||||
|
@ -328,20 +335,13 @@ def test_unprocessed_options(runner):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_deprecated_in_help_messages(runner):
|
@pytest.mark.parametrize("doc", ["CLI HELP", None])
|
||||||
@click.command(deprecated=True)
|
def test_deprecated_in_help_messages(runner, doc):
|
||||||
def cmd_with_help():
|
@click.command(deprecated=True, help=doc)
|
||||||
"""CLI HELP"""
|
def cli():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
result = runner.invoke(cmd_with_help, ["--help"])
|
result = runner.invoke(cli, ["--help"])
|
||||||
assert "(Deprecated)" in result.output
|
|
||||||
|
|
||||||
@click.command(deprecated=True)
|
|
||||||
def cmd_without_help():
|
|
||||||
pass
|
|
||||||
|
|
||||||
result = runner.invoke(cmd_without_help, ["--help"])
|
|
||||||
assert "(Deprecated)" in result.output
|
assert "(Deprecated)" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
@ -352,3 +352,62 @@ def test_deprecated_in_invocation(runner):
|
||||||
|
|
||||||
result = runner.invoke(deprecated_cmd)
|
result = runner.invoke(deprecated_cmd)
|
||||||
assert "DeprecationWarning:" in result.output
|
assert "DeprecationWarning:" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_command_parse_args_collects_option_prefixes():
|
||||||
|
@click.command()
|
||||||
|
@click.option("+p", is_flag=True)
|
||||||
|
@click.option("!e", is_flag=True)
|
||||||
|
def test(p, e):
|
||||||
|
pass
|
||||||
|
|
||||||
|
ctx = click.Context(test)
|
||||||
|
test.parse_args(ctx, [])
|
||||||
|
|
||||||
|
assert ctx._opt_prefixes == {"-", "--", "+", "!"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_parse_args_collects_base_option_prefixes():
|
||||||
|
@click.group()
|
||||||
|
@click.option("~t", is_flag=True)
|
||||||
|
def group(t):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@group.command()
|
||||||
|
@click.option("+p", is_flag=True)
|
||||||
|
def command1(p):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@group.command()
|
||||||
|
@click.option("!e", is_flag=True)
|
||||||
|
def command2(e):
|
||||||
|
pass
|
||||||
|
|
||||||
|
ctx = click.Context(group)
|
||||||
|
group.parse_args(ctx, ["command1", "+p"])
|
||||||
|
|
||||||
|
assert ctx._opt_prefixes == {"-", "--", "~"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_invoke_collects_used_option_prefixes(runner):
|
||||||
|
opt_prefixes = set()
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.option("~t", is_flag=True)
|
||||||
|
def group(t):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@group.command()
|
||||||
|
@click.option("+p", is_flag=True)
|
||||||
|
@click.pass_context
|
||||||
|
def command1(ctx, p):
|
||||||
|
nonlocal opt_prefixes
|
||||||
|
opt_prefixes = ctx._opt_prefixes
|
||||||
|
|
||||||
|
@group.command()
|
||||||
|
@click.option("!e", is_flag=True)
|
||||||
|
def command2(e):
|
||||||
|
pass
|
||||||
|
|
||||||
|
runner.invoke(group, ["command1"])
|
||||||
|
assert opt_prefixes == {"-", "--", "~", "+"}
|
||||||
|
|
|
@ -365,3 +365,11 @@ def test_parameter_source(runner, option_args, invoke_args, expect):
|
||||||
|
|
||||||
rv = runner.invoke(cli, standalone_mode=False, **invoke_args)
|
rv = runner.invoke(cli, standalone_mode=False, **invoke_args)
|
||||||
assert rv.return_value == expect
|
assert rv.return_value == expect
|
||||||
|
|
||||||
|
|
||||||
|
def test_propagate_opt_prefixes():
|
||||||
|
parent = click.Context(click.Command("test"))
|
||||||
|
parent._opt_prefixes = {"-", "--", "!"}
|
||||||
|
ctx = click.Context(click.Command("test2"), parent=parent)
|
||||||
|
|
||||||
|
assert ctx._opt_prefixes == {"-", "--", "!"}
|
||||||
|
|
|
@ -322,12 +322,13 @@ def test_global_show_default(runner):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
result = runner.invoke(cli, ["--help"])
|
result = runner.invoke(cli, ["--help"])
|
||||||
|
# the default to "--help" is not shown because it is False
|
||||||
assert result.output.splitlines() == [
|
assert result.output.splitlines() == [
|
||||||
"Usage: cli [OPTIONS]",
|
"Usage: cli [OPTIONS]",
|
||||||
"",
|
"",
|
||||||
"Options:",
|
"Options:",
|
||||||
" -f TEXT Output file name [default: out.txt]",
|
" -f TEXT Output file name [default: out.txt]",
|
||||||
" --help Show this message and exit. [default: False]",
|
" --help Show this message and exit.",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -266,3 +266,10 @@ def test_context():
|
||||||
"ignore_unknown_options": False,
|
"ignore_unknown_options": False,
|
||||||
"auto_envvar_prefix": None,
|
"auto_envvar_prefix": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_paramtype_no_name():
|
||||||
|
class TestType(click.ParamType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert TestType().to_info_dict()["name"] == "TestType"
|
||||||
|
|
|
@ -153,14 +153,15 @@ def test_init_bad_default_list(runner, multiple, nargs, default):
|
||||||
click.Option(["-a"], type=type, multiple=multiple, nargs=nargs, default=default)
|
click.Option(["-a"], type=type, multiple=multiple, nargs=nargs, default=default)
|
||||||
|
|
||||||
|
|
||||||
def test_empty_envvar(runner):
|
@pytest.mark.parametrize("env_key", ["MYPATH", "AUTO_MYPATH"])
|
||||||
|
def test_empty_envvar(runner, env_key):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--mypath", type=click.Path(exists=True), envvar="MYPATH")
|
@click.option("--mypath", type=click.Path(exists=True), envvar="MYPATH")
|
||||||
def cli(mypath):
|
def cli(mypath):
|
||||||
click.echo(f"mypath: {mypath}")
|
click.echo(f"mypath: {mypath}")
|
||||||
|
|
||||||
result = runner.invoke(cli, [], env={"MYPATH": ""})
|
result = runner.invoke(cli, env={env_key: ""}, auto_envvar_prefix="AUTO")
|
||||||
assert result.exit_code == 0
|
assert result.exception is None
|
||||||
assert result.output == "mypath: None\n"
|
assert result.output == "mypath: None\n"
|
||||||
|
|
||||||
|
|
||||||
|
@ -305,6 +306,19 @@ def test_dynamic_default_help_text(runner):
|
||||||
assert "(current user)" in result.output
|
assert "(current user)" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_default_help_special_method(runner):
|
||||||
|
class Value:
|
||||||
|
def __call__(self):
|
||||||
|
return 42
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "special value"
|
||||||
|
|
||||||
|
opt = click.Option(["-a"], default=Value(), show_default=True)
|
||||||
|
ctx = click.Context(click.Command("cli"))
|
||||||
|
assert "special value" in opt.get_help_record(ctx)[1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("type", "expect"),
|
("type", "expect"),
|
||||||
[
|
[
|
||||||
|
@ -489,6 +503,15 @@ def test_missing_option_string_cast():
|
||||||
assert str(excinfo.value) == "Missing parameter: a"
|
assert str(excinfo.value) == "Missing parameter: a"
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_required_flag(runner):
|
||||||
|
cli = click.Command(
|
||||||
|
"cli", params=[click.Option(["--on/--off"], is_flag=True, required=True)]
|
||||||
|
)
|
||||||
|
result = runner.invoke(cli)
|
||||||
|
assert result.exit_code == 2
|
||||||
|
assert "Error: Missing option '--on'." in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_missing_choice(runner):
|
def test_missing_choice(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--foo", type=click.Choice(["foo", "bar"]), required=True)
|
@click.option("--foo", type=click.Choice(["foo", "bar"]), required=True)
|
||||||
|
@ -630,7 +653,6 @@ def test_option_custom_class_reusable(runner):
|
||||||
|
|
||||||
# Both of the commands should have the --help option now.
|
# Both of the commands should have the --help option now.
|
||||||
for cmd in (cmd1, cmd2):
|
for cmd in (cmd1, cmd2):
|
||||||
|
|
||||||
result = runner.invoke(cmd, ["--help"])
|
result = runner.invoke(cmd, ["--help"])
|
||||||
assert "I am a help text" in result.output
|
assert "I am a help text" in result.output
|
||||||
assert "you wont see me" not in result.output
|
assert "you wont see me" not in result.output
|
||||||
|
@ -723,16 +745,37 @@ def test_show_default_boolean_flag_name(runner, default, expect):
|
||||||
assert f"[default: {expect}]" in message
|
assert f"[default: {expect}]" in message
|
||||||
|
|
||||||
|
|
||||||
def test_show_default_boolean_flag_value(runner):
|
def test_show_true_default_boolean_flag_value(runner):
|
||||||
"""When a boolean flag only has one opt, it will show the default
|
"""When a boolean flag only has one opt and its default is True,
|
||||||
value, not the opt name.
|
it will show the default value, not the opt name.
|
||||||
"""
|
"""
|
||||||
opt = click.Option(
|
opt = click.Option(
|
||||||
("--cache",), is_flag=True, show_default=True, help="Enable the cache."
|
("--cache",),
|
||||||
|
is_flag=True,
|
||||||
|
show_default=True,
|
||||||
|
default=True,
|
||||||
|
help="Enable the cache.",
|
||||||
)
|
)
|
||||||
ctx = click.Context(click.Command("test"))
|
ctx = click.Context(click.Command("test"))
|
||||||
message = opt.get_help_record(ctx)[1]
|
message = opt.get_help_record(ctx)[1]
|
||||||
assert "[default: False]" in message
|
assert "[default: True]" in message
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("default", [False, None])
|
||||||
|
def test_hide_false_default_boolean_flag_value(runner, default):
|
||||||
|
"""When a boolean flag only has one opt and its default is False or
|
||||||
|
None, it will not show the default
|
||||||
|
"""
|
||||||
|
opt = click.Option(
|
||||||
|
("--cache",),
|
||||||
|
is_flag=True,
|
||||||
|
show_default=True,
|
||||||
|
default=default,
|
||||||
|
help="Enable the cache.",
|
||||||
|
)
|
||||||
|
ctx = click.Context(click.Command("test"))
|
||||||
|
message = opt.get_help_record(ctx)[1]
|
||||||
|
assert "[default: " not in message
|
||||||
|
|
||||||
|
|
||||||
def test_show_default_string(runner):
|
def test_show_default_string(runner):
|
||||||
|
@ -761,6 +804,28 @@ def test_do_not_show_default_empty_multiple():
|
||||||
assert message == "values"
|
assert message == "values"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("ctx_value", "opt_value", "expect"),
|
||||||
|
[
|
||||||
|
(None, None, False),
|
||||||
|
(None, False, False),
|
||||||
|
(None, True, True),
|
||||||
|
(False, None, False),
|
||||||
|
(False, False, False),
|
||||||
|
(False, True, True),
|
||||||
|
(True, None, True),
|
||||||
|
(True, False, False),
|
||||||
|
(True, True, True),
|
||||||
|
(False, "one", True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_show_default_precedence(ctx_value, opt_value, expect):
|
||||||
|
ctx = click.Context(click.Command("test"), show_default=ctx_value)
|
||||||
|
opt = click.Option("-a", default=1, help="value", show_default=opt_value)
|
||||||
|
help = opt.get_help_record(ctx)[1]
|
||||||
|
assert ("default:" in help) is expect
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("args", "expect"),
|
("args", "expect"),
|
||||||
[
|
[
|
||||||
|
@ -839,3 +904,21 @@ def test_type_from_flag_value():
|
||||||
)
|
)
|
||||||
def test_is_bool_flag_is_correctly_set(option, expected):
|
def test_is_bool_flag_is_correctly_set(option, expected):
|
||||||
assert option.is_bool_flag is expected
|
assert option.is_bool_flag is expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("kwargs", "message"),
|
||||||
|
[
|
||||||
|
({"count": True, "multiple": True}, "'count' is not valid with 'multiple'."),
|
||||||
|
({"count": True, "is_flag": True}, "'count' is not valid with 'is_flag'."),
|
||||||
|
(
|
||||||
|
{"multiple": True, "is_flag": True},
|
||||||
|
"'multiple' is not valid with 'is_flag', use 'count'.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_invalid_flag_combinations(runner, kwargs, message):
|
||||||
|
with pytest.raises(TypeError) as e:
|
||||||
|
click.Option(["-a"], **kwargs)
|
||||||
|
|
||||||
|
assert message in str(e.value)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import click
|
||||||
|
from click.parser import OptionParser
|
||||||
from click.parser import split_arg_string
|
from click.parser import split_arg_string
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,3 +17,16 @@ from click.parser import split_arg_string
|
||||||
)
|
)
|
||||||
def test_split_arg_string(value, expect):
|
def test_split_arg_string(value, expect):
|
||||||
assert split_arg_string(value) == expect
|
assert split_arg_string(value) == expect
|
||||||
|
|
||||||
|
|
||||||
|
def test_parser_default_prefixes():
|
||||||
|
parser = OptionParser()
|
||||||
|
assert parser._opt_prefixes == {"-", "--"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parser_collects_prefixes():
|
||||||
|
ctx = click.Context(click.Command("test"))
|
||||||
|
parser = OptionParser(ctx)
|
||||||
|
click.Option("+p", is_flag=True).add_to_parser(parser, ctx)
|
||||||
|
click.Option("!e", is_flag=True).add_to_parser(parser, ctx)
|
||||||
|
assert parser._opt_prefixes == {"-", "--", "+", "!"}
|
||||||
|
|
|
@ -110,6 +110,45 @@ def test_type_choice():
|
||||||
assert _get_words(cli, ["-c"], "a2") == ["a2"]
|
assert _get_words(cli, ["-c"], "a2") == ["a2"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_choice_special_characters():
|
||||||
|
cli = Command("cli", params=[Option(["-c"], type=Choice(["!1", "!2", "+3"]))])
|
||||||
|
assert _get_words(cli, ["-c"], "") == ["!1", "!2", "+3"]
|
||||||
|
assert _get_words(cli, ["-c"], "!") == ["!1", "!2"]
|
||||||
|
assert _get_words(cli, ["-c"], "!2") == ["!2"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_choice_conflicting_prefix():
|
||||||
|
cli = Command(
|
||||||
|
"cli",
|
||||||
|
params=[
|
||||||
|
Option(["-c"], type=Choice(["!1", "!2", "+3"])),
|
||||||
|
Option(["+p"], is_flag=True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert _get_words(cli, ["-c"], "") == ["!1", "!2", "+3"]
|
||||||
|
assert _get_words(cli, ["-c"], "+") == ["+p"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_option_count():
|
||||||
|
cli = Command("cli", params=[Option(["-c"], count=True)])
|
||||||
|
assert _get_words(cli, ["-c"], "") == []
|
||||||
|
assert _get_words(cli, ["-c"], "-") == ["--help"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_option_optional():
|
||||||
|
cli = Command(
|
||||||
|
"cli",
|
||||||
|
add_help_option=False,
|
||||||
|
params=[
|
||||||
|
Option(["--name"], is_flag=False, flag_value="value"),
|
||||||
|
Option(["--flag"], is_flag=True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert _get_words(cli, ["--name"], "") == []
|
||||||
|
assert _get_words(cli, ["--name"], "-") == ["--flag"]
|
||||||
|
assert _get_words(cli, ["--name", "--flag"], "-") == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("type", "expect"),
|
("type", "expect"),
|
||||||
[(File(), "file"), (Path(), "file"), (Path(file_okay=False), "dir")],
|
[(File(), "file"), (Path(), "file"), (Path(file_okay=False), "dir")],
|
||||||
|
@ -161,20 +200,6 @@ def test_option_custom():
|
||||||
assert _get_words(cli, ["a", "b"], "c") == ["C"]
|
assert _get_words(cli, ["a", "b"], "c") == ["C"]
|
||||||
|
|
||||||
|
|
||||||
def test_autocompletion_deprecated():
|
|
||||||
# old function takes args and not param, returns all values, can mix
|
|
||||||
# strings and tuples
|
|
||||||
def custom(ctx, args, incomplete):
|
|
||||||
assert isinstance(args, list)
|
|
||||||
return [("art", "x"), "bat", "cat"]
|
|
||||||
|
|
||||||
with pytest.deprecated_call():
|
|
||||||
cli = Command("cli", params=[Argument(["x"], autocompletion=custom)])
|
|
||||||
|
|
||||||
assert _get_words(cli, [], "") == ["art", "bat", "cat"]
|
|
||||||
assert _get_words(cli, [], "c") == ["cat"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_option_multiple():
|
def test_option_multiple():
|
||||||
cli = Command(
|
cli = Command(
|
||||||
"type",
|
"type",
|
||||||
|
|
|
@ -420,17 +420,22 @@ def test_prompt_required_false(runner, args, expect):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("prompt", "input", "expect"),
|
("prompt", "input", "default", "expect"),
|
||||||
[
|
[
|
||||||
(True, "password\npassword", "password"),
|
(True, "password\npassword", None, "password"),
|
||||||
("Confirm Password", "password\npassword\n", "password"),
|
("Confirm Password", "password\npassword\n", None, "password"),
|
||||||
(False, None, None),
|
(True, "", "", ""),
|
||||||
|
(False, None, None, None),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_confirmation_prompt(runner, prompt, input, expect):
|
def test_confirmation_prompt(runner, prompt, input, default, expect):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"--password", prompt=prompt, hide_input=True, confirmation_prompt=prompt
|
"--password",
|
||||||
|
prompt=prompt,
|
||||||
|
hide_input=True,
|
||||||
|
default=default,
|
||||||
|
confirmation_prompt=prompt,
|
||||||
)
|
)
|
||||||
def cli(password):
|
def cli(password):
|
||||||
return password
|
return password
|
||||||
|
|
|
@ -320,6 +320,22 @@ def test_open_file(runner):
|
||||||
assert result.output == "foobar\nmeep\n"
|
assert result.output == "foobar\nmeep\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_file_pathlib_dash(runner):
|
||||||
|
@click.command()
|
||||||
|
@click.argument(
|
||||||
|
"filename", type=click.Path(allow_dash=True, path_type=pathlib.Path)
|
||||||
|
)
|
||||||
|
def cli(filename):
|
||||||
|
click.echo(str(type(filename)))
|
||||||
|
|
||||||
|
with click.open_file(filename) as f:
|
||||||
|
click.echo(f.read())
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ["-"], input="value")
|
||||||
|
assert result.exception is None
|
||||||
|
assert result.output == "pathlib.Path\nvalue\n"
|
||||||
|
|
||||||
|
|
||||||
def test_open_file_ignore_errors_stdin(runner):
|
def test_open_file_ignore_errors_stdin(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("filename")
|
@click.argument("filename")
|
||||||
|
@ -428,20 +444,12 @@ class MockMain:
|
||||||
("example.py", None, "example.py"),
|
("example.py", None, "example.py"),
|
||||||
(str(pathlib.Path("/foo/bar/example.py")), None, "example.py"),
|
(str(pathlib.Path("/foo/bar/example.py")), None, "example.py"),
|
||||||
("example", None, "example"),
|
("example", None, "example"),
|
||||||
(
|
(str(pathlib.Path("example/__main__.py")), "example", "python -m example"),
|
||||||
str(pathlib.Path("example/__main__.py")),
|
(str(pathlib.Path("example/cli.py")), "example", "python -m example.cli"),
|
||||||
MockMain(".example"),
|
|
||||||
"python -m example",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
str(pathlib.Path("example/cli.py")),
|
|
||||||
MockMain(".example"),
|
|
||||||
"python -m example.cli",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_detect_program_name(path, main, expected):
|
def test_detect_program_name(path, main, expected):
|
||||||
assert click.utils._detect_program_name(path, _main=main) == expected
|
assert click.utils._detect_program_name(path, _main=MockMain(main)) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_expand_args(monkeypatch):
|
def test_expand_args(monkeypatch):
|
||||||
|
@ -452,6 +460,8 @@ def test_expand_args(monkeypatch):
|
||||||
assert "setup.cfg" in click.utils._expand_args(["*.cfg"])
|
assert "setup.cfg" in click.utils._expand_args(["*.cfg"])
|
||||||
assert os.path.join("docs", "conf.py") in click.utils._expand_args(["**/conf.py"])
|
assert os.path.join("docs", "conf.py") in click.utils._expand_args(["**/conf.py"])
|
||||||
assert "*.not-found" in click.utils._expand_args(["*.not-found"])
|
assert "*.not-found" in click.utils._expand_args(["*.not-found"])
|
||||||
|
# a bad glob pattern, such as a pytest identifier, should return itself
|
||||||
|
assert click.utils._expand_args(["test.py::test_bad"])[0] == "test.py::test_bad"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
Loading…
Reference in a new issue