New upstream version 8.1.3
This commit is contained in:
parent
4555a76448
commit
e3834772a6
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
|
@ -1,8 +1,9 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: pip
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "08:00"
|
||||
open-pull-requests-limit: 99
|
||||
interval: "monthly"
|
||||
day: "monday"
|
||||
time: "16:00"
|
||||
timezone: "UTC"
|
||||
|
|
6
.github/workflows/lock.yaml
vendored
6
.github/workflows/lock.yaml
vendored
|
@ -8,8 +8,8 @@ jobs:
|
|||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
- uses: dessant/lock-threads@v3
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: 14
|
||||
pr-lock-inactive-days: 14
|
||||
issue-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
|
||||
matrix:
|
||||
include:
|
||||
- {name: Linux, python: '3.9', os: ubuntu-latest, tox: py39}
|
||||
- {name: Windows, python: '3.9', os: windows-latest, tox: py39}
|
||||
- {name: Mac, python: '3.9', os: macos-latest, tox: py39}
|
||||
- {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310}
|
||||
- {name: Windows, python: '3.10', os: windows-latest, tox: py310}
|
||||
- {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.7', python: '3.7', os: ubuntu-latest, tox: py37}
|
||||
- {name: '3.6', python: '3.6', os: ubuntu-latest, tox: py36}
|
||||
- {name: 'PyPy', python: pypy3, os: ubuntu-latest, tox: pypy3}
|
||||
- {name: Typing, python: '3.9', os: ubuntu-latest, tox: typing}
|
||||
- {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37}
|
||||
- {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements/*.txt'
|
||||
- name: update pip
|
||||
run: |
|
||||
pip install -U wheel
|
||||
pip install -U setuptools
|
||||
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
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
/.idea/
|
||||
/.vscode/
|
||||
.DS_Store/
|
||||
/env/
|
||||
/venv/
|
||||
__pycache__/
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
ci:
|
||||
autoupdate_branch: "8.1.x"
|
||||
autoupdate_schedule: monthly
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.29.0
|
||||
rev: v2.32.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ["--py36-plus"]
|
||||
args: ["--py37-plus"]
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v2.6.0
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ["--application-directories", "src"]
|
||||
additional_dependencies: ["setuptools>60.9"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.9b0
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 3.9.2
|
||||
rev: 4.0.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-bugbear
|
||||
- flake8-implicit-str-concat
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
rev: v4.2.0
|
||||
hooks:
|
||||
- id: fix-byte-order-marker
|
||||
- id: trailing-whitespace
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
version: 2
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
python:
|
||||
install:
|
||||
- requirements: requirements/docs.txt
|
||||
|
|
110
CHANGES.rst
110
CHANGES.rst
|
@ -1,5 +1,115 @@
|
|||
.. 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
|
||||
-------------
|
||||
|
||||
|
|
BIN
docs/.DS_Store
vendored
BIN
docs/.DS_Store
vendored
Binary file not shown.
|
@ -63,8 +63,6 @@ Utilities
|
|||
|
||||
.. autofunction:: pause
|
||||
|
||||
.. autofunction:: get_terminal_size
|
||||
|
||||
.. autofunction:: get_binary_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'])
|
||||
|
||||
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
|
||||
-------------------
|
||||
|
||||
|
@ -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
|
||||
:ref:`callback-evaluation-order`.
|
||||
|
||||
A callback is a function that is invoked with two parameters: the current
|
||||
:class:`Context` and the value. The context provides some useful features
|
||||
such as quitting the application and gives access to other already
|
||||
processed parameters.
|
||||
A callback is a function that is invoked with three parameters: the
|
||||
current :class:`Context`, the current :class:`Parameter`, and the value.
|
||||
The context provides some useful features such as quitting the
|
||||
application and gives access to other already processed parameters.
|
||||
|
||||
Here an example for a ``--version`` flag:
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ functional at least on a basic level even if everything is completely
|
|||
broken.
|
||||
|
||||
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`.
|
||||
|
||||
The echo function also supports color and other styles in output. It
|
||||
|
|
|
@ -132,6 +132,8 @@ with the incomplete value.
|
|||
.. code-block:: python
|
||||
|
||||
class EnvVarType(ParamType):
|
||||
name = "envvar"
|
||||
|
||||
def shell_complete(self, ctx, param, incomplete):
|
||||
return [
|
||||
CompletionItem(name)
|
||||
|
|
|
@ -9,4 +9,4 @@ Click Examples
|
|||
through the wrong interpreter.
|
||||
|
||||
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 tests.in
|
||||
-r typing.in
|
||||
pip-tools
|
||||
pip-compile-multi
|
||||
pre-commit
|
||||
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:
|
||||
#
|
||||
# pip-compile requirements/dev.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
alabaster==0.7.12
|
||||
# via sphinx
|
||||
attrs==21.2.0
|
||||
# via pytest
|
||||
babel==2.9.1
|
||||
# via sphinx
|
||||
backports.entry-points-selectable==1.1.0
|
||||
# via virtualenv
|
||||
certifi==2021.5.30
|
||||
# via requests
|
||||
-r docs.txt
|
||||
-r tests.txt
|
||||
-r typing.txt
|
||||
cfgv==3.3.1
|
||||
# via pre-commit
|
||||
charset-normalizer==2.0.6
|
||||
# via requests
|
||||
click==8.0.1
|
||||
# via pip-tools
|
||||
distlib==0.3.3
|
||||
# via virtualenv
|
||||
docutils==0.16
|
||||
click==8.1.2
|
||||
# via
|
||||
# sphinx
|
||||
# sphinx-tabs
|
||||
filelock==3.3.0
|
||||
# pip-compile-multi
|
||||
# pip-tools
|
||||
distlib==0.3.4
|
||||
# via virtualenv
|
||||
filelock==3.6.0
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
identify==2.3.0
|
||||
identify==2.5.0
|
||||
# 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
|
||||
# via pre-commit
|
||||
packaging==21.0
|
||||
# via
|
||||
# pallets-sphinx-themes
|
||||
# pytest
|
||||
# sphinx
|
||||
# tox
|
||||
pallets-sphinx-themes==2.0.1
|
||||
# via -r requirements/docs.in
|
||||
pep517==0.11.0
|
||||
pep517==0.12.0
|
||||
# via pip-tools
|
||||
pip-tools==6.3.0
|
||||
pip-compile-multi==2.4.5
|
||||
# via -r requirements/dev.in
|
||||
platformdirs==2.4.0
|
||||
pip-tools==6.6.0
|
||||
# via pip-compile-multi
|
||||
platformdirs==2.5.2
|
||||
# via virtualenv
|
||||
pluggy==1.0.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
pre-commit==2.15.0
|
||||
pre-commit==2.18.1
|
||||
# via -r requirements/dev.in
|
||||
py==1.10.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
|
||||
pyyaml==6.0
|
||||
# via pre-commit
|
||||
requests==2.26.0
|
||||
# via sphinx
|
||||
six==1.16.0
|
||||
# via
|
||||
# tox
|
||||
# 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
|
||||
# via
|
||||
# mypy
|
||||
# pre-commit
|
||||
# pytest
|
||||
# tox
|
||||
tomli==1.2.1
|
||||
# via pep517
|
||||
tox==3.24.4
|
||||
toposort==1.7
|
||||
# via pip-compile-multi
|
||||
tox==3.25.0
|
||||
# via -r requirements/dev.in
|
||||
typing-extensions==3.10.0.2
|
||||
# via mypy
|
||||
urllib3==1.26.7
|
||||
# via requests
|
||||
virtualenv==20.8.1
|
||||
virtualenv==20.14.1
|
||||
# via
|
||||
# pre-commit
|
||||
# tox
|
||||
wheel==0.37.0
|
||||
wheel==0.37.1
|
||||
# via pip-tools
|
||||
|
||||
# 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:
|
||||
#
|
||||
# pip-compile requirements/docs.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
alabaster==0.7.12
|
||||
# via sphinx
|
||||
babel==2.9.1
|
||||
babel==2.10.1
|
||||
# via sphinx
|
||||
certifi==2021.5.30
|
||||
certifi==2021.10.8
|
||||
# via requests
|
||||
charset-normalizer==2.0.6
|
||||
charset-normalizer==2.0.12
|
||||
# via requests
|
||||
docutils==0.16
|
||||
docutils==0.17.1
|
||||
# via
|
||||
# sphinx
|
||||
# sphinx-tabs
|
||||
idna==3.2
|
||||
idna==3.3
|
||||
# via requests
|
||||
imagesize==1.2.0
|
||||
imagesize==1.3.0
|
||||
# via sphinx
|
||||
jinja2==3.0.2
|
||||
jinja2==3.1.2
|
||||
# via sphinx
|
||||
markupsafe==2.0.1
|
||||
markupsafe==2.1.1
|
||||
# via jinja2
|
||||
packaging==21.0
|
||||
packaging==21.3
|
||||
# via
|
||||
# pallets-sphinx-themes
|
||||
# sphinx
|
||||
pallets-sphinx-themes==2.0.1
|
||||
pallets-sphinx-themes==2.0.2
|
||||
# via -r requirements/docs.in
|
||||
pygments==2.10.0
|
||||
pygments==2.12.0
|
||||
# via
|
||||
# sphinx
|
||||
# sphinx-tabs
|
||||
pyparsing==2.4.7
|
||||
pyparsing==3.0.8
|
||||
# via packaging
|
||||
pytz==2021.3
|
||||
pytz==2022.1
|
||||
# via babel
|
||||
requests==2.26.0
|
||||
requests==2.27.1
|
||||
# via sphinx
|
||||
snowballstemmer==2.1.0
|
||||
snowballstemmer==2.2.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
|
||||
sphinx==4.5.0
|
||||
# via
|
||||
# -r requirements/docs.in
|
||||
# pallets-sphinx-themes
|
||||
# sphinx-issues
|
||||
# sphinx-tabs
|
||||
# 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
|
||||
# via sphinx
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
|
@ -67,8 +68,5 @@ sphinxcontrib-qthelp==1.0.3
|
|||
# via sphinx
|
||||
sphinxcontrib-serializinghtml==1.1.5
|
||||
# via sphinx
|
||||
urllib3==1.26.7
|
||||
urllib3==1.26.9
|
||||
# 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:
|
||||
#
|
||||
# pip-compile requirements/tests.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
attrs==21.2.0
|
||||
attrs==21.4.0
|
||||
# via pytest
|
||||
iniconfig==1.1.1
|
||||
# via pytest
|
||||
packaging==21.0
|
||||
packaging==21.3
|
||||
# via pytest
|
||||
pluggy==1.0.0
|
||||
# via pytest
|
||||
py==1.10.0
|
||||
py==1.11.0
|
||||
# via pytest
|
||||
pyparsing==2.4.7
|
||||
pyparsing==3.0.8
|
||||
# via packaging
|
||||
pytest==6.2.5
|
||||
pytest==7.1.2
|
||||
# via -r requirements/tests.in
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
# 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:
|
||||
#
|
||||
# pip-compile requirements/typing.in
|
||||
# pip-compile-multi
|
||||
#
|
||||
mypy==0.950
|
||||
# via -r requirements/typing.in
|
||||
mypy-extensions==0.4.3
|
||||
# via mypy
|
||||
mypy==0.910
|
||||
# via -r requirements/typing.in
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
# via mypy
|
||||
typing-extensions==3.10.0.2
|
||||
typing-extensions==4.2.0
|
||||
# via mypy
|
||||
|
|
15
setup.cfg
15
setup.cfg
|
@ -29,8 +29,8 @@ classifiers =
|
|||
[options]
|
||||
packages = find:
|
||||
package_dir = = src
|
||||
include_package_data = true
|
||||
python_requires = >= 3.6
|
||||
include_package_data = True
|
||||
python_requires = >= 3.7
|
||||
# Dependencies are in setup.py for GitHub's dependency graph.
|
||||
|
||||
[options.packages.find]
|
||||
|
@ -42,14 +42,14 @@ filterwarnings =
|
|||
error
|
||||
|
||||
[coverage:run]
|
||||
branch = true
|
||||
branch = True
|
||||
source =
|
||||
click
|
||||
tests
|
||||
|
||||
[coverage:paths]
|
||||
source =
|
||||
click
|
||||
src
|
||||
*/site-packages
|
||||
|
||||
[flake8]
|
||||
|
@ -57,7 +57,7 @@ source =
|
|||
# E = pycodestyle errors
|
||||
# F = flake8 pyflakes
|
||||
# W = pycodestyle warnings
|
||||
# B9 = bugbear opinions,
|
||||
# B9 = bugbear opinions
|
||||
# ISC = implicit str concat
|
||||
select = B, E, F, W, B9, ISC
|
||||
ignore =
|
||||
|
@ -72,12 +72,13 @@ ignore =
|
|||
# up to 88 allowed by bugbear B950
|
||||
max-line-length = 80
|
||||
per-file-ignores =
|
||||
# __init__ module exports names
|
||||
# __init__ exports names
|
||||
src/click/__init__.py: F401
|
||||
|
||||
[mypy]
|
||||
files = src/click
|
||||
python_version = 3.6
|
||||
python_version = 3.7
|
||||
show_error_codes = True
|
||||
disallow_subclassing_any = True
|
||||
disallow_untyped_calls = True
|
||||
disallow_untyped_defs = True
|
||||
|
|
|
@ -41,7 +41,6 @@ from .termui import clear as clear
|
|||
from .termui import confirm as confirm
|
||||
from .termui import echo_via_pager as echo_via_pager
|
||||
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 launch as launch
|
||||
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 get_app_dir as get_app_dir
|
||||
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 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]:
|
||||
binary = "b" in mode
|
||||
|
||||
# Standard streams first. These are simple because they don't need
|
||||
# special handling for the atomic flag. It's entirely ignored.
|
||||
if filename == "-":
|
||||
# Standard streams first. These are simple because they ignore the
|
||||
# atomic flag. Use fsdecode to handle Path("-").
|
||||
if os.fsdecode(filename) == "-":
|
||||
if any(m in mode for m in ["w", "a", "x"]):
|
||||
if binary:
|
||||
return get_binary_stdout(), False
|
||||
|
@ -561,7 +561,6 @@ if sys.platform.startswith("win") and WIN:
|
|||
|
||||
return rv
|
||||
|
||||
|
||||
else:
|
||||
|
||||
def _get_argv_encoding() -> str:
|
||||
|
|
|
@ -675,7 +675,6 @@ if WIN:
|
|||
_translate_ch_to_exc(rv)
|
||||
return rv
|
||||
|
||||
|
||||
else:
|
||||
import tty
|
||||
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 errno
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
import typing as t
|
||||
from collections import abc
|
||||
from contextlib import contextmanager
|
||||
|
@ -14,7 +14,6 @@ from gettext import ngettext
|
|||
from itertools import repeat
|
||||
|
||||
from . import types
|
||||
from ._unicodefun import _verify_python_env
|
||||
from .exceptions import Abort
|
||||
from .exceptions import BadParameter
|
||||
from .exceptions import ClickException
|
||||
|
@ -224,9 +223,14 @@ class Context:
|
|||
codes are used in texts that Click prints which is by
|
||||
default not the case. This for instance would affect
|
||||
help output.
|
||||
:param show_default: Show defaults for all options. If not set,
|
||||
defaults to the value from a parent context. Overrides an
|
||||
option's ``show_default`` argument.
|
||||
:param show_default: Show the default value for commands. If this
|
||||
value is not set, it defaults to the value from the parent
|
||||
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
|
||||
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
|
||||
#: to implement nested parsing.
|
||||
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:
|
||||
obj = parent.obj
|
||||
|
@ -632,13 +638,13 @@ class Context:
|
|||
self.obj = rv = object_type()
|
||||
return rv
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def lookup_default(
|
||||
self, name: str, call: "te.Literal[True]" = True
|
||||
) -> t.Optional[t.Any]:
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def lookup_default(
|
||||
self, name: str, call: "te.Literal[False]" = ...
|
||||
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
|
||||
|
@ -956,7 +962,7 @@ class BaseCommand:
|
|||
|
||||
return results
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def main(
|
||||
self,
|
||||
args: t.Optional[t.Sequence[str]] = None,
|
||||
|
@ -967,7 +973,7 @@ class BaseCommand:
|
|||
) -> "te.NoReturn":
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def main(
|
||||
self,
|
||||
args: t.Optional[t.Sequence[str]] = None,
|
||||
|
@ -1029,10 +1035,6 @@ class BaseCommand:
|
|||
.. versionchanged:: 3.0
|
||||
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:
|
||||
args = sys.argv[1:]
|
||||
|
||||
|
@ -1133,13 +1135,6 @@ class Command(BaseCommand):
|
|||
Click. A basic command handles command line parsing and might dispatch
|
||||
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 context_settings: an optional dictionary with defaults that are
|
||||
passed to the context object.
|
||||
|
@ -1161,6 +1156,20 @@ class Command(BaseCommand):
|
|||
|
||||
:param deprecated: issues a message indicating that
|
||||
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__(
|
||||
|
@ -1186,12 +1195,6 @@ class Command(BaseCommand):
|
|||
#: should show up in the help page and execute. Eager parameters
|
||||
#: will automatically be handled before non eager ones.
|
||||
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.epilog = epilog
|
||||
self.options_metavar = options_metavar
|
||||
|
@ -1299,10 +1302,12 @@ class Command(BaseCommand):
|
|||
"""Gets short help for the command or makes it by shortening the
|
||||
long help string.
|
||||
"""
|
||||
text = self.short_help or ""
|
||||
|
||||
if not text and self.help:
|
||||
if self.short_help:
|
||||
text = inspect.cleandoc(self.short_help)
|
||||
elif self.help:
|
||||
text = make_default_short_help(self.help, limit)
|
||||
else:
|
||||
text = ""
|
||||
|
||||
if self.deprecated:
|
||||
text = _("(Deprecated) {text}").format(text=text)
|
||||
|
@ -1328,12 +1333,13 @@ class Command(BaseCommand):
|
|||
|
||||
def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None:
|
||||
"""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:
|
||||
text = _("(Deprecated) {text}").format(text=text)
|
||||
|
||||
if text:
|
||||
text = inspect.cleandoc(text).partition("\f")[0]
|
||||
formatter.write_paragraph()
|
||||
|
||||
with formatter.indentation():
|
||||
|
@ -1354,9 +1360,11 @@ class Command(BaseCommand):
|
|||
def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None:
|
||||
"""Writes the epilog into the formatter if it exists."""
|
||||
if self.epilog:
|
||||
epilog = inspect.cleandoc(self.epilog)
|
||||
formatter.write_paragraph()
|
||||
|
||||
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]:
|
||||
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._opt_prefixes.update(parser._opt_prefixes)
|
||||
return args
|
||||
|
||||
def invoke(self, ctx: Context) -> t.Any:
|
||||
|
@ -1568,17 +1577,6 @@ class MultiCommand(Command):
|
|||
|
||||
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:
|
||||
"""Extra format methods for multi methods that adds all the commands
|
||||
after the options.
|
||||
|
@ -1631,11 +1629,11 @@ class MultiCommand(Command):
|
|||
if not ctx.protected_args:
|
||||
if self.invoke_without_command:
|
||||
# No subcommand was invoked, so the result callback is
|
||||
# invoked with None for regular groups, or an empty list
|
||||
# for chained groups.
|
||||
# invoked with the group return value for regular
|
||||
# groups, or an empty list for chained groups.
|
||||
with ctx:
|
||||
super().invoke(ctx)
|
||||
return _process_result([] if self.chain else None)
|
||||
rv = super().invoke(ctx)
|
||||
return _process_result([] if self.chain else rv)
|
||||
ctx.fail(_("Missing command."))
|
||||
|
||||
# Fetch args back out
|
||||
|
@ -1811,9 +1809,19 @@ class Group(MultiCommand):
|
|||
_check_multicommand(self, name, cmd, register=True)
|
||||
self.commands[name] = cmd
|
||||
|
||||
@t.overload
|
||||
def command(self, __func: t.Callable[..., t.Any]) -> Command:
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def command(
|
||||
self, *args: t.Any, **kwargs: t.Any
|
||||
) -> 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
|
||||
the group. This takes the same arguments as :func:`command` and
|
||||
immediately registers the created command with this group by
|
||||
|
@ -1822,24 +1830,49 @@ class Group(MultiCommand):
|
|||
To customize the command class used, set the
|
||||
:attr:`command_class` attribute.
|
||||
|
||||
.. versionchanged:: 8.1
|
||||
This decorator can be applied without parentheses.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Added the :attr:`command_class` attribute.
|
||||
"""
|
||||
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
|
||||
|
||||
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:
|
||||
cmd = command(*args, **kwargs)(f)
|
||||
cmd: Command = command(*args, **kwargs)(f)
|
||||
self.add_command(cmd)
|
||||
return cmd
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
|
||||
return decorator
|
||||
|
||||
@t.overload
|
||||
def group(self, __func: t.Callable[..., t.Any]) -> "Group":
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def group(
|
||||
self, *args: t.Any, **kwargs: t.Any
|
||||
) -> 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
|
||||
the group. This takes the same arguments as :func:`group` and
|
||||
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`
|
||||
attribute.
|
||||
|
||||
.. versionchanged:: 8.1
|
||||
This decorator can be applied without parentheses.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Added the :attr:`group_class` attribute.
|
||||
"""
|
||||
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:
|
||||
kwargs["cls"] = type(self)
|
||||
else:
|
||||
kwargs["cls"] = self.group_class
|
||||
|
||||
def decorator(f: t.Callable[..., t.Any]) -> "Group":
|
||||
cmd = group(*args, **kwargs)(f)
|
||||
cmd: Group = group(*args, **kwargs)(f)
|
||||
self.add_command(cmd)
|
||||
return cmd
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
|
||||
return decorator
|
||||
|
||||
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]],
|
||||
]
|
||||
] = None,
|
||||
autocompletion: t.Optional[
|
||||
t.Callable[
|
||||
[Context, t.List[str], str], t.List[t.Union[t.Tuple[str, str], str]]
|
||||
]
|
||||
] = None,
|
||||
) -> None:
|
||||
self.name, self.opts, self.secondary_opts = self._parse_decls(
|
||||
param_decls or (), expose_value
|
||||
|
@ -2048,36 +2091,6 @@ class Parameter:
|
|||
self.is_eager = is_eager
|
||||
self.metavar = metavar
|
||||
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
|
||||
|
||||
if __debug__:
|
||||
|
@ -2172,13 +2185,13 @@ class Parameter:
|
|||
|
||||
return metavar
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def get_default(
|
||||
self, ctx: Context, call: "te.Literal[True]" = True
|
||||
) -> t.Optional[t.Any]:
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def get_default(
|
||||
self, ctx: Context, call: bool = ...
|
||||
) -> 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.
|
||||
|
||||
:param show_default: controls if the default value should be shown on the
|
||||
help page. Normally, defaults are not shown. If this
|
||||
value is a string, it shows the string instead of the
|
||||
value. This is particularly useful for dynamic options.
|
||||
:param show_envvar: controls if an environment variable should be shown on
|
||||
the help page. Normally, environment variables
|
||||
are not 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 show_default: Show the default value for this option in its
|
||||
help text. Values are not shown by default, unless
|
||||
:attr:`Context.show_default` is ``True``. If this value is a
|
||||
string, it shows that string in parentheses instead of the
|
||||
actual value. This is particularly useful for dynamic options.
|
||||
For single option boolean flags, the default remains hidden if
|
||||
its value is ``False``.
|
||||
:param show_envvar: Controls if an environment variable should be
|
||||
shown on the help page. Normally, environment variables are not
|
||||
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
|
||||
value if it was prompted for. Can be set to a string instead of
|
||||
``True`` to customize the message.
|
||||
:param prompt_required: If set to ``False``, the user will be
|
||||
prompted for input only when the option was specified as a flag
|
||||
without a value.
|
||||
:param hide_input: if this is `True` then the input on the prompt will be
|
||||
hidden from the user. This is useful for password
|
||||
input.
|
||||
:param hide_input: If this is ``True`` then the input on the prompt
|
||||
will be hidden from the user. This is useful for password input.
|
||||
:param is_flag: forces this option to act as a flag. The default is
|
||||
auto detection.
|
||||
: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 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
|
||||
``type`` is detected from ``flag_value`` if given.
|
||||
"""
|
||||
|
@ -2444,7 +2471,7 @@ class Option(Parameter):
|
|||
def __init__(
|
||||
self,
|
||||
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,
|
||||
confirmation_prompt: t.Union[bool, str] = False,
|
||||
prompt_required: bool = True,
|
||||
|
@ -2461,6 +2488,9 @@ class Option(Parameter):
|
|||
show_envvar: bool = False,
|
||||
**attrs: t.Any,
|
||||
) -> None:
|
||||
if help:
|
||||
help = inspect.cleandoc(help)
|
||||
|
||||
default_is_missing = "default" not in attrs
|
||||
super().__init__(param_decls, type=type, multiple=multiple, **attrs)
|
||||
|
||||
|
@ -2472,7 +2502,7 @@ class Option(Parameter):
|
|||
elif prompt is False:
|
||||
prompt_text = None
|
||||
else:
|
||||
prompt_text = t.cast(str, prompt)
|
||||
prompt_text = prompt
|
||||
|
||||
self.prompt = prompt_text
|
||||
self.confirmation_prompt = confirmation_prompt
|
||||
|
@ -2499,7 +2529,7 @@ class Option(Parameter):
|
|||
# flag if flag_value is set.
|
||||
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
|
||||
|
||||
if flag_value is None:
|
||||
|
@ -2550,6 +2580,9 @@ class Option(Parameter):
|
|||
if self.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]:
|
||||
info_dict = super().to_info_dict()
|
||||
info_dict.update(
|
||||
|
@ -2711,16 +2744,23 @@ class Option(Parameter):
|
|||
finally:
|
||||
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 (
|
||||
default_value is not None and (self.show_default or ctx.show_default)
|
||||
):
|
||||
if self.show_default is not None:
|
||||
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:
|
||||
default_string = f"({self.show_default})"
|
||||
elif isinstance(default_value, (list, tuple)):
|
||||
default_string = ", ".join(str(d) for d in default_value)
|
||||
elif callable(default_value):
|
||||
elif inspect.isfunction(default_value):
|
||||
default_string = _("(dynamic)")
|
||||
elif self.is_bool_flag and self.secondary_opts:
|
||||
# For boolean flags that have distinct True/False opts,
|
||||
|
@ -2728,6 +2768,8 @@ class Option(Parameter):
|
|||
default_string = split_opt(
|
||||
(self.opts if self.default else self.secondary_opts)[0]
|
||||
)[1]
|
||||
elif self.is_bool_flag and not self.secondary_opts and not default_value:
|
||||
default_string = ""
|
||||
else:
|
||||
default_string = str(default_value)
|
||||
|
||||
|
@ -2753,13 +2795,13 @@ class Option(Parameter):
|
|||
|
||||
return ("; " if any_prefix_is_slash else " / ").join(rv), help
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def get_default(
|
||||
self, ctx: Context, call: "te.Literal[True]" = True
|
||||
) -> t.Optional[t.Any]:
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def get_default(
|
||||
self, ctx: Context, call: bool = ...
|
||||
) -> 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]]]:
|
||||
# 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
|
||||
# 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.
|
||||
if self.is_flag and not self.is_bool_flag:
|
||||
for param in ctx.command.params:
|
||||
|
@ -2821,7 +2863,10 @@ class Option(Parameter):
|
|||
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
|
||||
rv = os.environ.get(envvar)
|
||||
|
||||
return rv
|
||||
if rv:
|
||||
return rv
|
||||
|
||||
return None
|
||||
|
||||
def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]:
|
||||
rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx)
|
||||
|
|
|
@ -14,7 +14,7 @@ from .globals import get_current_context
|
|||
from .utils import echo
|
||||
|
||||
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:
|
||||
|
@ -121,43 +121,38 @@ def pass_meta_key(
|
|||
return decorator
|
||||
|
||||
|
||||
def _make_command(
|
||||
f: F,
|
||||
name: t.Optional[str],
|
||||
attrs: t.MutableMapping[str, t.Any],
|
||||
cls: t.Type[Command],
|
||||
CmdType = t.TypeVar("CmdType", bound=Command)
|
||||
|
||||
|
||||
@t.overload
|
||||
def command(
|
||||
__func: t.Callable[..., t.Any],
|
||||
) -> 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
|
||||
return cls(
|
||||
name=name or f.__name__.lower().replace("_", "-"),
|
||||
callback=f,
|
||||
params=params,
|
||||
**attrs,
|
||||
)
|
||||
@t.overload
|
||||
def command(
|
||||
name: t.Optional[str] = None,
|
||||
cls: t.Type[CmdType] = ...,
|
||||
**attrs: t.Any,
|
||||
) -> t.Callable[..., CmdType]:
|
||||
...
|
||||
|
||||
|
||||
def command(
|
||||
name: t.Optional[str] = None,
|
||||
name: t.Union[str, t.Callable[..., t.Any], None] = None,
|
||||
cls: t.Optional[t.Type[Command]] = None,
|
||||
**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
|
||||
callback. This will also automatically attach all decorated
|
||||
: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.
|
||||
|
||||
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
|
||||
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.
|
||||
:param cls: the command class to instantiate. This defaults to
|
||||
: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:
|
||||
cls = 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__
|
||||
return cmd
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
|
||||
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
|
||||
works otherwise the same as :func:`command` just that the `cls`
|
||||
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))
|
||||
|
||||
|
||||
|
@ -219,7 +283,7 @@ def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[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))
|
||||
return f
|
||||
|
||||
|
@ -240,10 +304,7 @@ def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
|||
def decorator(f: FC) -> FC:
|
||||
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||
option_attrs = attrs.copy()
|
||||
|
||||
if "help" in option_attrs:
|
||||
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
|
||||
OptionClass = option_attrs.pop("cls", Option)
|
||||
OptionClass = option_attrs.pop("cls", None) or Option
|
||||
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||
return f
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import typing
|
||||
import typing as t
|
||||
from threading import local
|
||||
|
||||
|
@ -9,12 +8,12 @@ if t.TYPE_CHECKING:
|
|||
_local = local()
|
||||
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
|
||||
...
|
||||
|
||||
|
||||
@typing.overload
|
||||
@t.overload
|
||||
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
|
||||
...
|
||||
|
||||
|
|
|
@ -102,10 +102,10 @@ _SOURCE_BASH = """\
|
|||
IFS=',' read type value <<< "$completion"
|
||||
|
||||
if [[ $type == 'dir' ]]; then
|
||||
COMREPLY=()
|
||||
COMPREPLY=()
|
||||
compopt -o dirnames
|
||||
elif [[ $type == 'file' ]]; then
|
||||
COMREPLY=()
|
||||
COMPREPLY=()
|
||||
compopt -o default
|
||||
elif [[ $type == 'plain' ]]; then
|
||||
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."""
|
||||
if not value:
|
||||
return False
|
||||
|
||||
c = value[0]
|
||||
# Allow "/" since that starts a path.
|
||||
return not c.isalnum() and c != "/"
|
||||
return c in ctx._opt_prefixes
|
||||
|
||||
|
||||
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.
|
||||
|
||||
: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):
|
||||
return False
|
||||
|
||||
if param.is_flag:
|
||||
if param.is_flag or param.count:
|
||||
return False
|
||||
|
||||
last_option = None
|
||||
|
@ -476,7 +475,7 @@ def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
|
|||
if index + 1 > param.nargs:
|
||||
break
|
||||
|
||||
if _start_of_option(arg):
|
||||
if _start_of_option(ctx, arg):
|
||||
last_option = arg
|
||||
|
||||
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.
|
||||
if incomplete == "=":
|
||||
incomplete = ""
|
||||
elif "=" in incomplete and _start_of_option(incomplete):
|
||||
elif "=" in incomplete and _start_of_option(ctx, incomplete):
|
||||
name, _, incomplete = incomplete.partition("=")
|
||||
args.append(name)
|
||||
|
||||
|
@ -559,7 +558,7 @@ def _resolve_incomplete(
|
|||
# even if they start with the option character. If it hasn't been
|
||||
# given and the incomplete arg looks like an option, the current
|
||||
# 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
|
||||
|
||||
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
|
||||
# value, the option will provide value completions.
|
||||
for param in params:
|
||||
if _is_incomplete_option(args, param):
|
||||
if _is_incomplete_option(ctx, args, param):
|
||||
return param, incomplete
|
||||
|
||||
# It's not an option name or value. The first argument without a
|
||||
|
|
|
@ -3,7 +3,6 @@ import io
|
|||
import itertools
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
import typing as t
|
||||
from gettext import gettext as _
|
||||
|
||||
|
@ -94,7 +93,7 @@ def prompt(
|
|||
"""Prompts a user for input. This is a convenience function that can
|
||||
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.
|
||||
|
||||
:param text: the text to show for the prompt.
|
||||
|
@ -160,7 +159,6 @@ def prompt(
|
|||
if confirmation_prompt is True:
|
||||
confirmation_prompt = _("Repeat for confirmation")
|
||||
|
||||
confirmation_prompt = t.cast(str, confirmation_prompt)
|
||||
confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
|
||||
|
||||
while True:
|
||||
|
@ -182,9 +180,9 @@ def prompt(
|
|||
if not confirmation_prompt:
|
||||
return result
|
||||
while True:
|
||||
confirmation_prompt = t.cast(str, confirmation_prompt)
|
||||
value2 = prompt_func(confirmation_prompt)
|
||||
if value2:
|
||||
is_empty = not value and not value2
|
||||
if value2 or is_empty:
|
||||
break
|
||||
if value == value2:
|
||||
return result
|
||||
|
@ -252,26 +250,6 @@ def confirm(
|
|||
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(
|
||||
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
|
||||
color: t.Optional[bool] = None,
|
||||
|
@ -627,7 +605,7 @@ def unstyle(text: str) -> str:
|
|||
|
||||
def secho(
|
||||
message: t.Optional[t.Any] = None,
|
||||
file: t.Optional[t.IO] = None,
|
||||
file: t.Optional[t.IO[t.AnyStr]] = None,
|
||||
nl: bool = True,
|
||||
err: bool = False,
|
||||
color: t.Optional[bool] = None,
|
||||
|
|
|
@ -464,16 +464,16 @@ class CliRunner:
|
|||
Added the ``temp_dir`` parameter.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
t = tempfile.mkdtemp(dir=temp_dir)
|
||||
os.chdir(t)
|
||||
dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var]
|
||||
os.chdir(dt)
|
||||
|
||||
try:
|
||||
yield t
|
||||
yield t.cast(str, dt)
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
if temp_dir is None:
|
||||
try:
|
||||
shutil.rmtree(t)
|
||||
shutil.rmtree(dt)
|
||||
except OSError: # noqa: B014
|
||||
pass
|
||||
|
|
|
@ -63,7 +63,14 @@ class ParamType:
|
|||
# The class name without the "ParamType" suffix.
|
||||
param_type = type(self).__name__.partition("ParamType")[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__(
|
||||
self,
|
||||
|
@ -724,7 +731,7 @@ class File(ParamType):
|
|||
|
||||
return f
|
||||
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(
|
||||
self, ctx: "Context", param: "Parameter", incomplete: str
|
||||
|
@ -744,30 +751,31 @@ class File(ParamType):
|
|||
|
||||
|
||||
class Path(ParamType):
|
||||
"""The path type is similar to the :class:`File` type but it performs
|
||||
different checks. First of all, instead of returning an open file
|
||||
handle it returns just the filename. Secondly, it can perform various
|
||||
basic checks about what the file or directory should be.
|
||||
"""The ``Path`` type is similar to the :class:`File` type, but
|
||||
returns the filename instead of an open file. Various checks can be
|
||||
enabled to validate the type of file and permissions.
|
||||
|
||||
:param exists: if set to true, the file or directory needs to exist for
|
||||
this value to be valid. If this is not required and a
|
||||
file does indeed not exist, then all further checks are
|
||||
silently skipped.
|
||||
:param file_okay: controls if a file is a possible value.
|
||||
:param dir_okay: controls if a directory is a possible value.
|
||||
:param writable: if true, a writable check is performed.
|
||||
:param exists: The file or directory needs to exist for the value to
|
||||
be valid. If this is not set to ``True``, and the file does not
|
||||
exist, then all further checks are silently skipped.
|
||||
:param file_okay: Allow a file as a value.
|
||||
:param dir_okay: Allow a directory as a value.
|
||||
:param readable: if true, a readable check is performed.
|
||||
:param resolve_path: if this is true, then the path is fully resolved
|
||||
before the value is passed onwards. This means
|
||||
that it's absolute and symlinks are resolved. It
|
||||
will not expand a tilde-prefix, as this is
|
||||
supposed to be done by the shell only.
|
||||
:param allow_dash: If this is set to `True`, a single dash to indicate
|
||||
standard streams is permitted.
|
||||
:param writable: if true, a writable check is performed.
|
||||
:param executable: if true, an executable check is performed.
|
||||
:param resolve_path: Make the value absolute and resolve any
|
||||
symlinks. A ``~`` is not expanded, as this is supposed to be
|
||||
done by the shell only.
|
||||
:param allow_dash: Allow a single dash as a value, which indicates
|
||||
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
|
||||
``None``, keep Python's default, which is ``str``. Useful to
|
||||
convert to :class:`pathlib.Path`.
|
||||
|
||||
.. versionchanged:: 8.1
|
||||
Added the ``executable`` parameter.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Allow passing ``type=pathlib.Path``.
|
||||
|
||||
|
@ -787,12 +795,14 @@ class Path(ParamType):
|
|||
resolve_path: bool = False,
|
||||
allow_dash: bool = False,
|
||||
path_type: t.Optional[t.Type] = None,
|
||||
executable: bool = False,
|
||||
):
|
||||
self.exists = exists
|
||||
self.file_okay = file_okay
|
||||
self.dir_okay = dir_okay
|
||||
self.writable = writable
|
||||
self.readable = readable
|
||||
self.writable = writable
|
||||
self.executable = executable
|
||||
self.resolve_path = resolve_path
|
||||
self.allow_dash = allow_dash
|
||||
self.type = path_type
|
||||
|
@ -865,12 +875,22 @@ class Path(ParamType):
|
|||
)
|
||||
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
||||
self.fail(
|
||||
_("{name} {filename!r} is a directory.").format(
|
||||
_("{name} '{filename}' is a directory.").format(
|
||||
name=self.name.title(), filename=os.fsdecode(value)
|
||||
),
|
||||
param,
|
||||
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):
|
||||
self.fail(
|
||||
_("{name} {filename!r} is not writable.").format(
|
||||
|
@ -879,9 +899,10 @@ class Path(ParamType):
|
|||
param,
|
||||
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(
|
||||
_("{name} {filename!r} is not readable.").format(
|
||||
_("{name} {filename!r} is not executable.").format(
|
||||
name=self.name.title(), filename=os.fsdecode(value)
|
||||
),
|
||||
param,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
import typing as t
|
||||
from functools import update_wrapper
|
||||
|
@ -203,7 +204,7 @@ class KeepOpenFile:
|
|||
|
||||
def echo(
|
||||
message: t.Optional[t.Any] = None,
|
||||
file: t.Optional[t.IO] = None,
|
||||
file: t.Optional[t.IO[t.Any]] = None,
|
||||
nl: bool = True,
|
||||
err: bool = False,
|
||||
color: t.Optional[bool] = None,
|
||||
|
@ -340,55 +341,45 @@ def open_file(
|
|||
lazy: bool = False,
|
||||
atomic: bool = False,
|
||||
) -> t.IO:
|
||||
"""This is similar to how the :class:`File` works but for manual
|
||||
usage. Files are opened non lazy by default. This can open regular
|
||||
files as well as stdin/stdout if ``'-'`` is passed.
|
||||
"""Open a file, with extra behavior to handle ``'-'`` to indicate
|
||||
a standard stream, lazy open on write, and atomic write. Similar to
|
||||
the behavior of the :class:`~click.File` param type.
|
||||
|
||||
If stdin/stdout is returned the stream is wrapped so that the context
|
||||
manager will not close the stream accidentally. This makes it possible
|
||||
to always use the function like this without having to worry to
|
||||
accidentally close a standard stream::
|
||||
If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
|
||||
wrapped so that using it in a context manager will not close it.
|
||||
This makes it possible to use the function without accidentally
|
||||
closing a standard stream:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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).
|
||||
: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.
|
||||
.. versionadded:: 3.0
|
||||
"""
|
||||
if lazy:
|
||||
return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic))
|
||||
|
||||
f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
|
||||
|
||||
if not should_close:
|
||||
f = t.cast(t.IO, KeepOpenFile(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(
|
||||
filename: t.Union[str, bytes, os.PathLike], shorten: bool = False
|
||||
) -> str:
|
||||
|
@ -484,7 +475,7 @@ class PacifyFlushWrapper:
|
|||
|
||||
|
||||
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:
|
||||
"""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
|
||||
|
@ -506,6 +497,9 @@ def _detect_program_name(
|
|||
|
||||
:meta private:
|
||||
"""
|
||||
if _main is None:
|
||||
_main = sys.modules["__main__"]
|
||||
|
||||
if not path:
|
||||
path = sys.argv[0]
|
||||
|
||||
|
@ -546,7 +540,7 @@ def _expand_args(
|
|||
See :func:`glob.glob`, :func:`os.path.expanduser`, and
|
||||
: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.
|
||||
|
||||
:param args: List of command line arguments to expand.
|
||||
|
@ -554,6 +548,10 @@ def _expand_args(
|
|||
:param env: Expand environment variables.
|
||||
: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
|
||||
|
||||
:meta private:
|
||||
|
@ -569,7 +567,10 @@ def _expand_args(
|
|||
if env:
|
||||
arg = os.path.expandvars(arg)
|
||||
|
||||
matches = glob(arg, recursive=glob_recursive)
|
||||
try:
|
||||
matches = glob(arg, recursive=glob_recursive)
|
||||
except re.error:
|
||||
matches = []
|
||||
|
||||
if not matches:
|
||||
out.append(arg)
|
||||
|
|
|
@ -41,25 +41,25 @@ def test_nargs_tup(runner):
|
|||
assert result.output.splitlines() == ["name=peter", "point=1/2"]
|
||||
|
||||
|
||||
def test_nargs_tup_composite(runner):
|
||||
variations = [
|
||||
@pytest.mark.parametrize(
|
||||
"opts",
|
||||
[
|
||||
dict(type=(str, int)),
|
||||
dict(type=click.Tuple([str, int])),
|
||||
dict(nargs=2, type=click.Tuple([str, int])),
|
||||
dict(nargs=2, type=(str, int)),
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_nargs_tup_composite(runner, opts):
|
||||
@click.command()
|
||||
@click.argument("item", **opts)
|
||||
def copy(item):
|
||||
name, id = item
|
||||
click.echo(f"name={name} id={id:d}")
|
||||
|
||||
for opts in variations:
|
||||
|
||||
@click.command()
|
||||
@click.argument("item", **opts)
|
||||
def copy(item):
|
||||
name, id = item
|
||||
click.echo(f"name={name} id={id:d}")
|
||||
|
||||
result = runner.invoke(copy, ["peter", "1"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == ["name=peter id=1"]
|
||||
result = runner.invoke(copy, ["peter", "1"])
|
||||
assert result.exception is None
|
||||
assert result.output.splitlines() == ["name=peter id=1"]
|
||||
|
||||
|
||||
def test_nargs_err(runner):
|
||||
|
@ -120,9 +120,9 @@ def test_file_args(runner):
|
|||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_path_args(runner):
|
||||
def test_path_allow_dash(runner):
|
||||
@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):
|
||||
click.echo(input)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
import uuid
|
||||
from itertools import chain
|
||||
|
||||
import pytest
|
||||
|
@ -104,125 +103,135 @@ def test_group_from_list(runner):
|
|||
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.option("--foo", default="no value")
|
||||
def cli(foo):
|
||||
click.echo(f"FOO:[{foo}]")
|
||||
@click.option("--s", default="no value")
|
||||
def cli(s):
|
||||
click.echo(f"S:[{s}]")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert "FOO:[no value]" in result.output
|
||||
result = runner.invoke(cli, args)
|
||||
assert expect in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--foo=42"])
|
||||
assert not result.exception
|
||||
assert "FOO:[42]" in result.output
|
||||
|
||||
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
|
||||
if expect.startswith("Error:"):
|
||||
assert result.exception is not None
|
||||
else:
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
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.option("--foo", default=42)
|
||||
def cli(foo):
|
||||
click.echo(f"FOO:[{foo * 2}]")
|
||||
@click.option("--i", default=42)
|
||||
def cli(i):
|
||||
click.echo(f"I:[{i * 2}]")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert "FOO:[84]" in result.output
|
||||
result = runner.invoke(cli, args)
|
||||
assert expect in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--foo=23"])
|
||||
assert not result.exception
|
||||
assert "FOO:[46]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--foo=bar"])
|
||||
assert result.exception
|
||||
assert "Invalid value for '--foo': 'bar' is not a valid integer." in result.output
|
||||
if expect.startswith("Error:"):
|
||||
assert result.exception is not None
|
||||
else:
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
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.option(
|
||||
"--u", default="ba122011-349f-423b-873b-9d6a79c688ab", type=click.UUID
|
||||
)
|
||||
def cli(u):
|
||||
assert type(u) is uuid.UUID
|
||||
click.echo(f"U:[{u}]")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert "U:[ba122011-349f-423b-873b-9d6a79c688ab]" in result.output
|
||||
result = runner.invoke(cli, args)
|
||||
assert expect in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--u=821592c1-c50e-4971-9cd6-e89dc6832f86"])
|
||||
assert not result.exception
|
||||
assert "U:[821592c1-c50e-4971-9cd6-e89dc6832f86]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--u=bar"])
|
||||
assert result.exception
|
||||
assert "Invalid value for '--u': 'bar' is not a valid UUID." in result.output
|
||||
if expect.startswith("Error:"):
|
||||
assert result.exception is not None
|
||||
else:
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
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.option("--foo", default=42, type=click.FLOAT)
|
||||
def cli(foo):
|
||||
assert type(foo) is float
|
||||
click.echo(f"FOO:[{foo}]")
|
||||
@click.option("--f", default=42.0)
|
||||
def cli(f):
|
||||
click.echo(f"F:[{f}]")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert "FOO:[42.0]" in result.output
|
||||
result = runner.invoke(cli, args)
|
||||
assert expect in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--foo=23.5"])
|
||||
assert not result.exception
|
||||
assert "FOO:[23.5]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ["--foo=bar"])
|
||||
assert result.exception
|
||||
assert "Invalid value for '--foo': 'bar' is not a valid float." in result.output
|
||||
if expect.startswith("Error:"):
|
||||
assert result.exception is not None
|
||||
else:
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
def test_boolean_option(runner):
|
||||
for default in True, False:
|
||||
@pytest.mark.parametrize("default", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
("args", "expect"), [(["--on"], True), (["--off"], False), ([], None)]
|
||||
)
|
||||
def test_boolean_switch(runner, default, args, expect):
|
||||
@click.command()
|
||||
@click.option("--on/--off", default=default)
|
||||
def cli(on):
|
||||
return on
|
||||
|
||||
@click.command()
|
||||
@click.option("--with-foo/--without-foo", default=default)
|
||||
def cli(with_foo):
|
||||
click.echo(with_foo)
|
||||
if expect is None:
|
||||
expect = default
|
||||
|
||||
result = runner.invoke(cli, ["--with-foo"])
|
||||
assert not result.exception
|
||||
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"
|
||||
result = runner.invoke(cli, args, standalone_mode=False)
|
||||
assert result.return_value is expect
|
||||
|
||||
for default in True, False:
|
||||
|
||||
@click.command()
|
||||
@click.option("--flag", is_flag=True, default=default)
|
||||
def cli(flag):
|
||||
click.echo(flag)
|
||||
@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.option("--f", is_flag=True, default=default)
|
||||
def cli(f):
|
||||
return f
|
||||
|
||||
result = runner.invoke(cli, ["--flag"])
|
||||
assert not result.exception
|
||||
assert result.output == f"{not default}\n"
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == f"{default}\n"
|
||||
if default:
|
||||
expect = not expect
|
||||
|
||||
result = runner.invoke(cli, args, standalone_mode=False)
|
||||
assert result.return_value is expect
|
||||
|
||||
|
||||
@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)
|
||||
def cli():
|
||||
"""ROOT HELP"""
|
||||
|
@ -50,22 +60,9 @@ def test_chaining_help(runner):
|
|||
"""BDIST HELP"""
|
||||
click.echo("bdist called")
|
||||
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
result = runner.invoke(cli, args)
|
||||
assert not result.exception
|
||||
assert "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." 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
|
||||
assert expect in result.output
|
||||
|
||||
|
||||
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"]
|
||||
|
||||
|
||||
@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):
|
||||
"""When a group has ``invoke_without_command=True``, the result
|
||||
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)
|
||||
def cli():
|
||||
pass
|
||||
return 1
|
||||
|
||||
@cli.result_callback()
|
||||
def process_result(result):
|
||||
click.echo(str(result), nl=False)
|
||||
click.echo(result, nl=False)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.output == expect
|
||||
|
@ -127,15 +124,23 @@ def test_chaining_with_arguments(runner):
|
|||
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.option("-i", "--input", type=click.File("r"))
|
||||
def cli(input):
|
||||
@click.option("-f", type=click.File("r"))
|
||||
def cli(f):
|
||||
pass
|
||||
|
||||
@cli.result_callback()
|
||||
def process_pipeline(processors, input):
|
||||
iterator = (x.rstrip("\r\n") for x in input)
|
||||
def process_pipeline(processors, f):
|
||||
iterator = (x.rstrip("\r\n") for x in f)
|
||||
for processor in processors:
|
||||
iterator = processor(iterator)
|
||||
for item in iterator:
|
||||
|
@ -157,17 +162,9 @@ def test_pipeline(runner):
|
|||
|
||||
return processor
|
||||
|
||||
result = runner.invoke(cli, ["-i", "-"], input="foo\nbar")
|
||||
result = runner.invoke(cli, args, input=input)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == ["foo", "bar"]
|
||||
|
||||
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"]
|
||||
assert result.output.splitlines() == expect
|
||||
|
||||
|
||||
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 pytest
|
||||
|
||||
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):
|
||||
@click.command(no_args_is_help=True)
|
||||
def cli():
|
||||
|
@ -119,7 +135,16 @@ def test_default_maps(runner):
|
|||
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.argument("obj")
|
||||
def cli(obj):
|
||||
|
@ -129,21 +154,9 @@ def test_group_with_args(runner):
|
|||
def move():
|
||||
click.echo("move")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert "Show this message and exit." 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"
|
||||
result = runner.invoke(cli, args)
|
||||
assert result.exit_code == exit_code
|
||||
assert expect in result.output
|
||||
|
||||
|
||||
def test_base_command(runner):
|
||||
|
@ -196,18 +209,12 @@ def test_base_command(runner):
|
|||
|
||||
cli.add_command(OptParseCommand("test", parser, test_callback))
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["test", "-f", "test.txt", "-q", "whatever.txt", "whateverelse.txt"]
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
"whatever.txt whateverelse.txt",
|
||||
"test.txt",
|
||||
"False",
|
||||
]
|
||||
result = runner.invoke(cli, ["test", "-f", "f.txt", "-q", "q1.txt", "q2.txt"])
|
||||
assert result.exception is None
|
||||
assert result.output.splitlines() == ["q1.txt q2.txt", "f.txt", "False"]
|
||||
|
||||
result = runner.invoke(cli, ["test", "--help"])
|
||||
assert not result.exception
|
||||
assert result.exception is None
|
||||
assert result.output.splitlines() == [
|
||||
"Usage: foo test [OPTIONS]",
|
||||
"",
|
||||
|
@ -328,20 +335,13 @@ def test_unprocessed_options(runner):
|
|||
]
|
||||
|
||||
|
||||
def test_deprecated_in_help_messages(runner):
|
||||
@click.command(deprecated=True)
|
||||
def cmd_with_help():
|
||||
"""CLI HELP"""
|
||||
@pytest.mark.parametrize("doc", ["CLI HELP", None])
|
||||
def test_deprecated_in_help_messages(runner, doc):
|
||||
@click.command(deprecated=True, help=doc)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd_with_help, ["--help"])
|
||||
assert "(Deprecated)" in result.output
|
||||
|
||||
@click.command(deprecated=True)
|
||||
def cmd_without_help():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd_without_help, ["--help"])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert "(Deprecated)" in result.output
|
||||
|
||||
|
||||
|
@ -352,3 +352,62 @@ def test_deprecated_in_invocation(runner):
|
|||
|
||||
result = runner.invoke(deprecated_cmd)
|
||||
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)
|
||||
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
|
||||
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
# the default to "--help" is not shown because it is False
|
||||
assert result.output.splitlines() == [
|
||||
"Usage: cli [OPTIONS]",
|
||||
"",
|
||||
"Options:",
|
||||
" -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,
|
||||
"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)
|
||||
|
||||
|
||||
def test_empty_envvar(runner):
|
||||
@pytest.mark.parametrize("env_key", ["MYPATH", "AUTO_MYPATH"])
|
||||
def test_empty_envvar(runner, env_key):
|
||||
@click.command()
|
||||
@click.option("--mypath", type=click.Path(exists=True), envvar="MYPATH")
|
||||
def cli(mypath):
|
||||
click.echo(f"mypath: {mypath}")
|
||||
|
||||
result = runner.invoke(cli, [], env={"MYPATH": ""})
|
||||
assert result.exit_code == 0
|
||||
result = runner.invoke(cli, env={env_key: ""}, auto_envvar_prefix="AUTO")
|
||||
assert result.exception is None
|
||||
assert result.output == "mypath: None\n"
|
||||
|
||||
|
||||
|
@ -305,6 +306,19 @@ def test_dynamic_default_help_text(runner):
|
|||
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(
|
||||
("type", "expect"),
|
||||
[
|
||||
|
@ -489,6 +503,15 @@ def test_missing_option_string_cast():
|
|||
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):
|
||||
@click.command()
|
||||
@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.
|
||||
for cmd in (cmd1, cmd2):
|
||||
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert "I am a help text" 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
|
||||
|
||||
|
||||
def test_show_default_boolean_flag_value(runner):
|
||||
"""When a boolean flag only has one opt, it will show the default
|
||||
value, not the opt name.
|
||||
def test_show_true_default_boolean_flag_value(runner):
|
||||
"""When a boolean flag only has one opt and its default is True,
|
||||
it will show the default value, not the opt name.
|
||||
"""
|
||||
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"))
|
||||
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):
|
||||
|
@ -761,6 +804,28 @@ def test_do_not_show_default_empty_multiple():
|
|||
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(
|
||||
("args", "expect"),
|
||||
[
|
||||
|
@ -839,3 +904,21 @@ def test_type_from_flag_value():
|
|||
)
|
||||
def test_is_bool_flag_is_correctly_set(option, 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 click
|
||||
from click.parser import OptionParser
|
||||
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):
|
||||
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"]
|
||||
|
||||
|
||||
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(
|
||||
("type", "expect"),
|
||||
[(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"]
|
||||
|
||||
|
||||
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():
|
||||
cli = Command(
|
||||
"type",
|
||||
|
|
|
@ -420,17 +420,22 @@ def test_prompt_required_false(runner, args, expect):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("prompt", "input", "expect"),
|
||||
("prompt", "input", "default", "expect"),
|
||||
[
|
||||
(True, "password\npassword", "password"),
|
||||
("Confirm Password", "password\npassword\n", "password"),
|
||||
(False, None, None),
|
||||
(True, "password\npassword", None, "password"),
|
||||
("Confirm Password", "password\npassword\n", None, "password"),
|
||||
(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.option(
|
||||
"--password", prompt=prompt, hide_input=True, confirmation_prompt=prompt
|
||||
"--password",
|
||||
prompt=prompt,
|
||||
hide_input=True,
|
||||
default=default,
|
||||
confirmation_prompt=prompt,
|
||||
)
|
||||
def cli(password):
|
||||
return password
|
||||
|
|
|
@ -320,6 +320,22 @@ def test_open_file(runner):
|
|||
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):
|
||||
@click.command()
|
||||
@click.argument("filename")
|
||||
|
@ -428,20 +444,12 @@ class MockMain:
|
|||
("example.py", None, "example.py"),
|
||||
(str(pathlib.Path("/foo/bar/example.py")), None, "example.py"),
|
||||
("example", None, "example"),
|
||||
(
|
||||
str(pathlib.Path("example/__main__.py")),
|
||||
MockMain(".example"),
|
||||
"python -m example",
|
||||
),
|
||||
(
|
||||
str(pathlib.Path("example/cli.py")),
|
||||
MockMain(".example"),
|
||||
"python -m example.cli",
|
||||
),
|
||||
(str(pathlib.Path("example/__main__.py")), "example", "python -m example"),
|
||||
(str(pathlib.Path("example/cli.py")), "example", "python -m example.cli"),
|
||||
],
|
||||
)
|
||||
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):
|
||||
|
@ -452,6 +460,8 @@ def test_expand_args(monkeypatch):
|
|||
assert "setup.cfg" in click.utils._expand_args(["*.cfg"])
|
||||
assert os.path.join("docs", "conf.py") in click.utils._expand_args(["**/conf.py"])
|
||||
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(
|
||||
|
|
Loading…
Reference in a new issue