New upstream version 6.7+git20180829

This commit is contained in:
aviau 2018-09-06 14:55:10 -04:00
parent e5fe171691
commit 5dd28971f0
79 changed files with 3247 additions and 1718 deletions

28
.appveyor.yml Normal file
View file

@ -0,0 +1,28 @@
environment:
global:
TOXENV: py,codecov
matrix:
- PYTHON: C:\Python36-x64
- PYTHON: C:\Python27-x64
- PYTHON: C:\Python36
- PYTHON: C:\Python27
init:
- SET PATH=%PYTHON%;%PATH%
install:
- python -m pip install -U tox
build: false
test_script:
- python -m tox
branches:
only:
- master
- /^.*-maintenance$/
cache:
- '%LOCALAPPDATA%\pip\Cache'

3
.coveragerc Normal file
View file

@ -0,0 +1,3 @@
[run]
branch = true
source = click,tests

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
.DS_Store
*.pyc
*.pyo
*.egg-ignore
*.egg-info
.pytest_cache
dist
build
docs/_build
click.egg-info
venv/
.tox
.cache
.ropeproject
.idea

56
.travis.yml Normal file
View file

@ -0,0 +1,56 @@
os: linux
sudo: false
language: python
matrix:
include:
- python: 3.6
env: TOXENV=py,codecov
- python: 3.5
env: TOXENV=py,codecov
- python: 3.4
env: TOXENV=py,codecov
- python: 2.7
env: TOXENV=py,codecov
- python: pypy3
env: TOXENV=py,codecov
- python: nightly
env: TOXENV=py
- os: osx
language: generic
env: TOXENV=py3,py2,codecov
cache:
pip: false
directories:
- $HOME/Library/Caches/Homebrew
- $HOME/Library/Caches/pip
allow_failures:
- python: pypy3
- python: nightly
- os: osx
fast_finish: true
before_install:
- |
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
brew upgrade python
brew install python@2
export PATH="/usr/local/opt/python/libexec/bin:${PATH}"
fi
install:
- pip install tox
script:
- tox
cache:
- pip
branches:
only:
- master
- /^.*-maintenance/
notifications:
email: false

351
CHANGES
View file

@ -1,351 +0,0 @@
Click Changelog
===============
This contains all major version changes between Click releases.
Version 6.7
-----------
(bugfix release; released on January 6th 2017)
- Make `click.progressbar` work with `codecs.open` files. See #637.
- Fix bug in bash completion with nested subcommands. See #639.
- Fix test runner not saving caller env correctly. See #644.
- Fix handling of SIGPIPE. See #626
- Deal with broken Windows environments such as Google App Engine's. See #711.
Version 6.6
-----------
(bugfix release; released on April 4th 2016)
- Fix bug in `click.Path` where it would crash when passed a `-`. See #551.
Version 6.4
-----------
(bugfix release; released on March 24th 2016)
- Fix bug in bash completion where click would discard one or more trailing
arguments. See #471.
Version 6.3
-----------
(bugfix release; released on February 22 2016)
- Fix argument checks for interpreter invoke with `-m` and `-c`
on Windows.
- Fixed a bug that cased locale detection to error out on Python 3.
Version 6.2
-----------
(bugfix release, released on November 27th 2015)
- Correct fix for hidden progress bars.
Version 6.1
-----------
(bugfix release, released on November 27th 2015)
- Resolved an issue with invisible progress bars no longer rendering.
- Disable chain commands with subcommands as they were inherently broken.
- Fix `MissingParameter` not working without parameters passed.
Version 6.0
-----------
(codename "pow pow", released on November 24th 2015)
- Optimized the progressbar rendering to not render when it did not
actually change.
- Explicitly disallow nargs=-1 with a set default.
- The context is now closed before it's popped from the stack.
- Added support for short aliases for the false flag on toggles.
- Click will now attempt to aid you with debugging locale errors
better by listing with the help of the OS what locales are
available.
- Click used to return byte strings on Python 2 in some unit-testing
situations. This has been fixed to correctly return unicode strings
now.
- For Windows users on Python 2, Click will now handle Unicode more
correctly handle Unicode coming in from the system. This also has
the disappointing side effect that filenames will now be always
unicode by default in the `Path` type which means that this can
introduce small bugs for code not aware of this.
- Added a `type` parameter to `Path` to force a specific string type
on the value.
- For users running Python on Windows the `echo`) and `prompt` functions
now work with full unicode functionality in the Python windows console
by emulating an output stream. This also applies to getting the
virtual output and input streams via `click.get_text_stream(...)`.
- Unittests now always force a certain virtual terminal width.
- Added support for allowing dashes to indicate standard streams to the
`Path` type.
- Multi commands in chain mode no longer propagate arguments left over
from parsing to the callbacks. It's also now disallowed through an
exception when optional arguments are attached to multi commands if chain
mode is enabled.
- Relaxed restriction that disallowed chained commands to have other
chained commands as child commands.
- Arguments with positive nargs can now have defaults implemented.
Previously this configuration would often result in slightly unexpected
values be returned.
Version 5.1
-----------
(bugfix release, released on 17th August 2015)
- Fix a bug in `pass_obj` that would accidentally pass the context too.
Version 5.0
-----------
(codename "tok tok", released on 16th August 2015)
- Removed various deprecated functionality.
- Atomic files now only accept the `w` mode.
- Change the usage part of help output for very long commands to wrap
their arguments onto the next line, indented by 4 spaces.
- Fix a bug where return code and error messages were incorrect when
using ``CliRunner``.
- added `get_current_context`.
- added a `meta` dictionary to the context which is shared across the
linked list of contexts to allow click utilities to place state there.
- introduced `Context.scope`.
- The `echo` function is now threadsafe: It calls the `write` method of the
underlying object only once.
- `prompt(hide_input=True)` now prints a newline on `^C`.
- Click will now warn if users are using ``unicode_literals``.
- Click will now ignore the ``PAGER`` environment variable if it is empty or
contains only whitespace.
- The `click-contrib` GitHub organization was created.
Version 4.1
-----------
(bugfix release, released on July 14th 2015)
- Fix a bug where error messages would include a trailing `None` string.
- Fix a bug where Click would crash on docstrings with trailing newlines.
- Support streams with encoding set to `None` on Python 3 by barfing with
a better error.
- Handle ^C in less-pager properly.
- Handle return value of `None` from `sys.getfilesystemencoding`
- Fix crash when writing to unicode files with `click.echo`.
- Fix type inference with multiple options.
Version 4.0
-----------
(codename "zoom zoom", released on March 31st 2015)
- Added `color` parameters to lots of interfaces that directly or indirectly
call into echoing. This previously was always autodetection (with the
exception of the `echo_via_pager` function). Now you can forcefully
enable or disable it, overriding the auto detection of Click.
- Added an `UNPROCESSED` type which does not perform any type changes which
simplifies text handling on 2.x / 3.x in some special advanced usecases.
- Added `NoSuchOption` and `BadOptionUsage` exceptions for more generic
handling of errors.
- Added support for handling of unprocessed options which can be useful in
situations where arguments are forwarded to underlying tools.
- Added `max_content_width` parameter to the context which can be used to
change the maximum width of help output. By default Click will not format
content for more than 80 characters width.
- Added support for writing prompts to stderr.
- Fix a bug when showing the default for multiple arguments.
- Added support for custom subclasses to `option` and `argument`.
- Fix bug in ``clear()`` on Windows when colorama is installed.
- Reject ``nargs=-1`` for options properly. Options cannot be variadic.
- Fixed an issue with bash completion not working properly for commands with
non ASCII characters or dashes.
- Added a way to manually update the progressbar.
- Changed the formatting of missing arguments. Previously the internal
argument name was shown in error messages, now the metavar is shown if
passed. In case an automated metavar is selected, it's stripped of
extra formatting first.
Version 3.3
-----------
(bugfix release, released on September 8th 2014)
- Fixed an issue with error reporting on Python 3 for invalid forwarding
of commands.
Version 3.2
-----------
(bugfix release, released on August 22nd 2014)
- Added missing `err` parameter forwarding to the `secho` function.
- Fixed default parameters not being handled properly by the context
invoke method. This is a backwards incompatible change if the function
was used improperly. See :ref:`upgrade-to-3.2` for more information.
- Removed the `invoked_subcommands` attribute largely. It is not possible
to provide it to work error free due to how the parsing works so this
API has been deprecated. See :ref:`upgrade-to-3.2` for more information.
- Restored the functionality of `invoked_subcommand` which was broken as
a regression in 3.1.
Version 3.1
-----------
(bugfix release, released on August 13th 2014)
- Fixed a regression that caused contexts of subcommands to be
created before the parent command was invoked which was a
regression from earlier Click versions.
Version 3.0
-----------
(codename "clonk clonk", released on August 12th 2014)
- formatter now no longer attempts to accomodate for terminals
smaller than 50 characters. If that happens it just assumes
a minimal width.
- added a way to not swallow exceptions in the test system.
- added better support for colors with pagers and ways to
override the autodetection.
- the CLI runner's result object now has a traceback attached.
- improved automatic short help detection to work better with
dots that do not terminate sentences.
- when definining options without actual valid option strings
now, Click will give an error message instead of silently
passing. This should catch situations where users wanted to
created arguments instead of options.
- Restructured Click internally to support vendoring.
- Added support for multi command chaining.
- Added support for defaults on options with `multiple` and
options and arguments with `nargs != 1`.
- label passed to `progressbar` is no longer rendered with
whitespace stripped.
- added a way to disable the standalone mode of the `main`
method on a Click command to be able to handle errors better.
- added support for returning values from command callbacks.
- added simplifications for printing to stderr from `echo`.
- added result callbacks for groups.
- entering a context multiple times defers the cleanup until
the last exit occurs.
- added `open_file`.
Version 2.6
-----------
(bugfix release, released on August 11th 2014)
- Fixed an issue where the wrapped streams on Python 3 would be reporting
incorrect values for seekable.
Version 2.5
-----------
(bugfix release, released on July 28th 2014)
- Fixed a bug with text wrapping on Python 3.
Version 2.4
-----------
(bugfix release, released on July 4th 2014)
- Corrected a bug in the change of the help option in 2.3.
Version 2.3
-----------
(bugfix release, released on July 3rd 2014)
- Fixed an incorrectly formatted help record for count options.'
- Add support for ansi code stripping on Windows if colorama
is not available.
- restored the Click 1.0 handling of the help parameter for certain
edge cases.
Version 2.2
-----------
(bugfix release, released on June 26th 2014)
- fixed tty detection on PyPy.
- fixed an issue that progress bars were not rendered when the
context manager was entered.
Version 2.1
-----------
(bugfix release, released on June 14th 2014)
- fixed the :func:`launch` function on windows.
- improved the colorama support on windows to try hard to not
screw up the console if the application is interrupted.
- fixed windows terminals incorrectly being reported to be 80
characters wide instead of 79
- use colorama win32 bindings if available to get the correct
dimensions of a windows terminal.
- fixed an issue with custom function types on Python 3.
- fixed an issue with unknown options being incorrectly reported
in error messages.
Version 2.0
-----------
(codename "tap tap tap", released on June 6th 2014)
- added support for opening stdin/stdout on Windows in
binary mode correctly.
- added support for atomic writes to files by going through
a temporary file.
- introduced :exc:`BadParameter` which can be used to easily perform
custom validation with the same error messages as in the type system.
- added :func:`progressbar`; a function to show progress bars.
- added :func:`get_app_dir`; a function to calculate the home folder
for configs.
- Added transparent handling for ANSI codes into the :func:`echo`
function through `colorama`.
- Added :func:`clear` function.
- Breaking change: parameter callbacks now get the parameter object
passed as second argument. There is legacy support for old callbacks
which will warn but still execute the script.
- Added :func:`style`, :func:`unstyle` and :func:`secho` for ANSI
styles.
- Added an :func:`edit` function that invokes the default editor.
- Added an :func:`launch` function that launches browsers and applications.
- nargs of -1 for arguments can now be forced to be a single item through
the required flag. It defaults to not required.
- setting a default for arguments now implicitly makes it non required.
- changed "yN" / "Yn" to "y/N" and "Y/n" in confirmation prompts.
- added basic support for bash completion.
- added :func:`getchar` to fetch a single character from the terminal.
- errors now go to stderr as intended.
- fixed various issues with more exotic parameter formats like DOS/Windows
style arguments.
- added :func:`pause` which works similar to the Windows ``pause`` cmd
built-in but becomes an automatic noop if the application is not run
through a terminal.
- added a bit of extra information about missing choice parameters.
- changed how the help function is implemented to allow global overriding
of the help option.
- added support for token normalization to implement case insensitive handling.
- added support for providing defaults for context settings.
Version 1.1
-----------
(bugfix release, released on May 23rd 2014)
- fixed a bug that caused text files in Python 2 to not accept
native strings.
Version 1.0
-----------
(no codename, released on May 21st 2014)
- Initial release.

558
CHANGES.rst Normal file
View file

@ -0,0 +1,558 @@
Click Changelog
===============
This contains all major version changes between Click releases.
Version 7.0
-----------
(upcoming release with new features, release date to be decided)
- Non-standalone calls to Context.exit return the exit code, rather than
calling ``sys.exit`` (`#667`_)(`#533`_)
- Updated test env matrix. (`#1027`_)
- Fixes a ``ZeroDivisionError`` in ``ProgressBar.make_step``,
when the arg passed to the first call of ``ProgressBar.update`` is 0. (`#1012`_)(`#447`_)
- Document that options can be ``required=True``. (`#1022`_)(`#514`_)
- Fix path validation bug. (`#1020`_)(`#795`_)
- Document customizing option names. (`#1016`_)(`#725`_)
- Wrap ``click.Choice``'s missing message. (`#1000`_)(`#202`_)
- Don't add newlines by default for progressbars. (`#1013`_)
- Document how ``auto_envar_prefix`` works with command groups. (`#1011`_)
- Fix failing bash completion function test signature.
- Clarify how paramteres are named. (`#1009`_)(`#949`_)
- Document bytestripping behavior of ``CliRunner``. (`#1010`_)(`#334`_)
- Fix Google App Engine ``ImportError``. (`#995`_)
- Document that ANSI color info isn't parsedfrom bytearrays in Python 2. (`#334`_)
- Add note to documentation on how parameters are named.
- Fix formatting for short help. (`#1008`_)
- Extract bar formatting to its own method. (`#414`_)
- Move ``fcntl`` import. (`#965`_)
- Fixed issues where ``fd`` was undefined. (`#1007`_)
- Added deprecation flag to commands. (`#1005`_)
- Fix various Sphinx errors. (`#883`_)
- Add ``case_sensitive=False`` as an option to Choice types. (`#887`_)
- Add details about Python version support. (`#1004`_)
- Clarify documentation on command line options. (`#1003`_)(`#741`_)
- Add ``case_sensitive=False`` as an option to Choice. (`#569`_)
- Better handling of help text for dynamic default option values. (`#996`_)
- Allow short width to address cmd formatting. (`#1002`_)
- Add test case checking for custom param type. (`#1001`_)
- Make ``Argument.make_metavar()`` default to type metavar. (`#675`_)
- Show progressbar only if total execution time is visible. (`#487`_)
- Allow setting ``prog_name`` as extra in ``CliRunner.invoke`` (`#999`_)(`#616`_)
- Add support for Sphinx 1.7+ (`#991`_)
- Fix ``get_winter_size()`` so it correctly returns ``(0,0)``. (`#997`_)
- Update progress after iteration. (`#706`_)(`#651`_)
- Add ``show_envvar`` for showing environment variables in help. (`#710`_)
- Add support for bright colors. (`#809`_)
- Add documentation for ``ignore_unkown_options``. (`#684`_)
- Allow ``CliRunner`` to separate stdout and stderr. (`#868`_)
- Implement streaming pager. (`#889`_)(`#409`_)
- Progress bar now uses stderr by default. (`#863`_)
- Do not set options twice. (`#962`_)
- Add Py2/ unicode / str compatability for doc tools. (`#993`_)(`#719`_)
- Add copy option attrs so that custom classes can be re-used. (`#994`_)(`#926`_)
- ``param_hint`` in errors now derived from param itself. (`#709`_)(`#704`_)(`#598`_)
- Add a test that ensures that when an Argument is formatted into a usage error,
its metavar is used, not its name. (`#612`_)
- Fix variable precedence. (`#874`_)(`#873`_)
- Fix ``ResourceWarning`` that occurs during some tests. (`#878`_)
- Update README to match flask style and add ``long_description`` to setup.py. (`#990`_)
- Drop testing for 2.6 3.3 and 3.6.
- Make locale optional (`#880`_)
- Fix invalid escape sequences. (`#877`_)
- Added workaround for jupyter. (`#918`_)
- x and a filemodes now use stdout when file is ``'-'``. (`#929`_)
- ``_AtomicFile`` now uses the realpath of the original filename. (`#920`_)
- Fix missing comma in ``__all__`` list (`#935`_)
- Raw strings added so correct escaping occurs. (`#807`_)
- Add bool conversion for ``t`` and ``f``. (`#842`_)
- Update doc to match arg name for ``path_type``. (`#801`_)
- Add bright colors support for ``click.style``
and fix the reset option for parameters ``fg`` and ``bg``. (`#703`_)
- Add test and documentation for ``Option`` naming: functionality. (`#799`_)
- Use deterministic option name; can't rely on list sort. (`#794`_)(`#793`_)
- Added support for bash completions containing spaces. (`#773`_)
- Added support for dynamic bash completion from a user-supplied callback.
(`#755`_)
- Added support for bash completion of ``type=click.Choice`` for ``Options`` and
``Arguments``. (`#535`_)
- The user is now presented with the available choices if ``prompt=True`` and
``type=click.Choice`` in a ``click.option``. The choices are listed within
parenthesis like ``'Choose fruit (apple, orange): '``.
- The exception objects now store unicode properly.
- Added the ability to hide commands and options from help.
- Added Float Range in Types.
- ``secho``'s first argument can now be ``None``, like in ``echo``.
- Usage errors now hint at the ``--help`` option.
- ``launch`` now works properly under Cygwin. (`#650`_)
- ``CliRunner.invoke`` now may receive ``args`` as a string representing
a Unix shell command. See (`#664`_).
- Fix bug that caused bashcompletion to give improper completions on
chained commands. (`#774`_)
- Add support for bright colors.
- ``'t'`` and ``'f'`` are now converted to ``True`` and ``False``.
- Fix bug that caused bashcompletion to give improper completions on
chained commands when a required option/argument was being completed.
(`#790`_)(`#806`_)
- Allow autocompletion function to determine whether or not to return
completions that start with the incomplete argument.
- Add native ZSH autocompletion support. (`#323`_)(`#865`_)
- Add support for auto-completion documentation. See (`#866`_)(`#869`_)
- Subcommands that are named by the function now automatically have the
underscore replaced with a dash. So if you register a function named
``my_command`` it becomes ``my-command`` in the command line interface.
- Stdout is now automatically set to non blocking.
- Use realpath to convert atomic file internally into its full canonical
path so that changing working directories does not harm it.
- Force stdout/stderr writable. This works around issues with badly patched
standard streams like those from jupyter.
.. _#1027: https://github.com/pallets/click/pull/1027
.. _#1012: https://github.com/pallets/click/pull/1012
.. _#447: https://github.com/pallets/click/issues/447
.. _#1022: https://github.com/pallets/click/pull/1022
.. _#869: https://github.com/pallets/click/pull/869
.. _#866: https://github.com/pallets/click/issues/866
.. _#514: https://github.com/pallets/click/issues/514
.. _#1020: https://github.com/pallets/click/pull/1020
.. _#795: https://github.com/pallets/click/issues/795
.. _#1016: https://github.com/pallets/click/pull/1016
.. _#725: https://github.com/pallets/click/issues/725
.. _#1000: https://github.com/pallets/click/pull/1000
.. _#202: https://github.com/pallets/click/issues/202
.. _#1013: https://github.com/pallets/click/pull/1013
.. _#1011: https://github.com/pallets/click/pull/1011
.. _#865: https://github.com/pallets/click/pull/865
.. _#323: https://github.com/pallets/click/issues/323
.. _#1009: https://github.com/pallets/click/pull/1009
.. _#949: https://github.com/pallets/click/issues/949
.. _#1010: https://github.com/pallets/click/pull/1010
.. _#334: https://github.com/pallets/click/issues/334
.. _#995: https://github.com/pallets/click/pull/995
.. _#1008: https://github.com/pallets/click/pull/1008
.. _#414: https://github.com/pallets/click/pull/414
.. _#965: https://github.com/pallets/click/pull/965
.. _#1005: https://github.com/pallets/click/pull/1005
.. _#883: https://github.com/pallets/click/pull/883
.. _#887: https://github.com/pallets/click/pull/887
.. _#1004: https://github.com/pallets/click/pull/1004
.. _#1003: https://github.com/pallets/click/pull/1003
.. _#741: https://github.com/pallets/click/issues/741
.. _#569: https://github.com/pallets/click/pull/569
.. _#1007: https://github.com/pallets/click/pull/1007
.. _#996: https://github.com/pallets/click/pull/996
.. _#1002: https://github.com/pallets/click/pull/1002
.. _#1001: https://github.com/pallets/click/pull/1001
.. _#675: https://github.com/pallets/click/pull/675
.. _#487: https://github.com/pallets/click/pull/487
.. _#999: https://github.com/pallets/click/pull/999
.. _#616: https://github.com/pallets/click/issues/616
.. _#991: https://github.com/pallets/click/pull/991
.. _#997: https://github.com/pallets/click/pull/997
.. _#706: https://github.com/pallets/click/pull/706
.. _#651: https://github.com/pallets/click/issues/651
.. _#710: https://github.com/pallets/click/pull/710
.. _#809: https://github.com/pallets/click/pull/809
.. _#868: https://github.com/pallets/click/pull/868
.. _#889: https://github.com/pallets/click/pull/889
.. _#409: https://github.com/pallets/click/issues/409
.. _#863: https://github.com/pallets/click/pull/863
.. _#962: https://github.com/pallets/click/pull/962
.. _#993: https://github.com/pallets/click/pull/993
.. _#994: https://github.com/pallets/click/pull/994
.. _#926: https://github.com/pallets/click/issues/926
.. _#709: https://github.com/pallets/click/pull/709
.. _#612: https://github.com/pallets/click/pull/612
.. _#704: https://github.com/pallets/click/issues/704
.. _#598: https://github.com/pallets/click/issues/598
.. _#719: https://github.com/pallets/click/issues/719
.. _#874: https://github.com/pallets/click/pull/874
.. _#873: https://github.com/pallets/click/issues/873
.. _#990: https://github.com/pallets/click/pull/990
.. _#684: https://github.com/pallets/click/pull/684
.. _#878: https://github.com/pallets/click/pull/878
.. _#880: https://github.com/pallets/click/issues/880
.. _#877: https://github.com/pallets/click/pull/877
.. _#918: https://github.com/pallets/click/pull/918
.. _#929: https://github.com/pallets/click/pull/929
.. _#920: https://github.com/pallets/click/pull/920
.. _#935: https://github.com/pallets/click/pull/935
.. _#807: https://github.com/pallets/click/pull/807
.. _#806: https://github.com/pallets/click/pull/806
.. _#842: https://github.com/pallets/click/pull/842
.. _#801: https://github.com/pallets/click/pull/801
.. _#703: https://github.com/pallets/click/issues/703
.. _#799: https://github.com/pallets/click/pull/799
.. _#794: https://github.com/pallets/click/pull/794
.. _#793: https://github.com/pallets/click/issues/793
.. _#773: https://github.com/pallets/click/pull/773
.. _#755: https://github.com/pallets/click/pull/755
.. _#535: https://github.com/pallets/click/pull/535
.. _#650: https://github.com/pallets/click/pull/650
.. _#664: https://github.com/pallets/click/pull/664
.. _#774: https://github.com/pallets/click/pull/774
.. _#790: https://github.com/pallets/click/pull/790
Version 6.8
-----------
(bugfix release; yet to be released)
- Disabled ``sys._getframes()`` on Python interpreters that don't support it. See
#728.
- Fix bug in test runner when calling ``sys.exit`` with ``None``. See #739.
- Fix crash on Windows console, see #744.
- Fix bashcompletion on chained commands. See #754.
- Fix option naming routine to match documentation. See #793
- Fixed the behavior of click error messages with regards to unicode on 2.x
and 3.x respectively. Message is now always unicode and the str and unicode
special methods work as you expect on that platform.
Version 6.7
-----------
(bugfix release; released on January 6th 2017)
- Make ``click.progressbar`` work with ``codecs.open`` files. See #637.
- Fix bug in bash completion with nested subcommands. See #639.
- Fix test runner not saving caller env correctly. See #644.
- Fix handling of SIGPIPE. See #626
- Deal with broken Windows environments such as Google App Engine's. See #711.
Version 6.6
-----------
(bugfix release; released on April 4th 2016)
- Fix bug in ``click.Path`` where it would crash when passed a ``-``. See #551.
Version 6.4
-----------
(bugfix release; released on March 24th 2016)
- Fix bug in bash completion where click would discard one or more trailing
arguments. See #471.
Version 6.3
-----------
(bugfix release; released on February 22 2016)
- Fix argument checks for interpreter invoke with ``-m`` and ``-c``
on Windows.
- Fixed a bug that cased locale detection to error out on Python 3.
Version 6.2
-----------
(bugfix release, released on November 27th 2015)
- Correct fix for hidden progress bars.
Version 6.1
-----------
(bugfix release, released on November 27th 2015)
- Resolved an issue with invisible progress bars no longer rendering.
- Disable chain commands with subcommands as they were inherently broken.
- Fix ``MissingParameter`` not working without parameters passed.
Version 6.0
-----------
(codename "pow pow", released on November 24th 2015)
- Optimized the progressbar rendering to not render when it did not
actually change.
- Explicitly disallow ``nargs=-1`` with a set default.
- The context is now closed before it's popped from the stack.
- Added support for short aliases for the false flag on toggles.
- Click will now attempt to aid you with debugging locale errors
better by listing with the help of the OS what locales are
available.
- Click used to return byte strings on Python 2 in some unit-testing
situations. This has been fixed to correctly return unicode strings
now.
- For Windows users on Python 2, Click will now handle Unicode more
correctly handle Unicode coming in from the system. This also has
the disappointing side effect that filenames will now be always
unicode by default in the ``Path`` type which means that this can
introduce small bugs for code not aware of this.
- Added a ``type`` parameter to ``Path`` to force a specific string type
on the value.
- For users running Python on Windows the ``echo`` and ``prompt`` functions
now work with full unicode functionality in the Python windows console
by emulating an output stream. This also applies to getting the
virtual output and input streams via ``click.get_text_stream(...)``.
- Unittests now always force a certain virtual terminal width.
- Added support for allowing dashes to indicate standard streams to the
``Path`` type.
- Multi commands in chain mode no longer propagate arguments left over
from parsing to the callbacks. It's also now disallowed through an
exception when optional arguments are attached to multi commands if chain
mode is enabled.
- Relaxed restriction that disallowed chained commands to have other
chained commands as child commands.
- Arguments with positive nargs can now have defaults implemented.
Previously this configuration would often result in slightly unexpected
values be returned.
Version 5.1
-----------
(bugfix release, released on 17th August 2015)
- Fix a bug in ``pass_obj`` that would accidentally pass the context too.
Version 5.0
-----------
(codename "tok tok", released on 16th August 2015)
- Removed various deprecated functionality.
- Atomic files now only accept the ``w`` mode.
- Change the usage part of help output for very long commands to wrap
their arguments onto the next line, indented by 4 spaces.
- Fix a bug where return code and error messages were incorrect when
using ``CliRunner``.
- added ``get_current_context``.
- added a ``meta`` dictionary to the context which is shared across the
linked list of contexts to allow click utilities to place state there.
- introduced ``Context.scope``.
- The ``echo`` function is now threadsafe: It calls the ``write`` method of the
underlying object only once.
- ``prompt(hide_input=True)`` now prints a newline on ``^C``.
- Click will now warn if users are using ``unicode_literals``.
- Click will now ignore the ``PAGER`` environment variable if it is empty or
contains only whitespace.
- The ``click-contrib`` GitHub organization was created.
Version 4.1
-----------
(bugfix release, released on July 14th 2015)
- Fix a bug where error messages would include a trailing ``None`` string.
- Fix a bug where Click would crash on docstrings with trailing newlines.
- Support streams with encoding set to ``None`` on Python 3 by barfing with
a better error.
- Handle ^C in less-pager properly.
- Handle return value of ``None`` from ``sys.getfilesystemencoding``
- Fix crash when writing to unicode files with ``click.echo``.
- Fix type inference with multiple options.
Version 4.0
-----------
(codename "zoom zoom", released on March 31st 2015)
- Added ``color`` parameters to lots of interfaces that directly or indirectly
call into echoing. This previously was always autodetection (with the
exception of the ``echo_via_pager`` function). Now you can forcefully
enable or disable it, overriding the auto detection of Click.
- Added an ``UNPROCESSED`` type which does not perform any type changes which
simplifies text handling on 2.x / 3.x in some special advanced usecases.
- Added ``NoSuchOption`` and ``BadOptionUsage`` exceptions for more generic
handling of errors.
- Added support for handling of unprocessed options which can be useful in
situations where arguments are forwarded to underlying tools.
- Added ``max_content_width`` parameter to the context which can be used to
change the maximum width of help output. By default Click will not format
content for more than 80 characters width.
- Added support for writing prompts to stderr.
- Fix a bug when showing the default for multiple arguments.
- Added support for custom subclasses to ``option`` and ``argument``.
- Fix bug in ``clear()`` on Windows when colorama is installed.
- Reject ``nargs=-1`` for options properly. Options cannot be variadic.
- Fixed an issue with bash completion not working properly for commands with
non ASCII characters or dashes.
- Added a way to manually update the progressbar.
- Changed the formatting of missing arguments. Previously the internal
argument name was shown in error messages, now the metavar is shown if
passed. In case an automated metavar is selected, it's stripped of
extra formatting first.
Version 3.3
-----------
(bugfix release, released on September 8th 2014)
- Fixed an issue with error reporting on Python 3 for invalid forwarding
of commands.
Version 3.2
-----------
(bugfix release, released on August 22nd 2014)
- Added missing ``err`` parameter forwarding to the ``secho`` function.
- Fixed default parameters not being handled properly by the context
invoke method. This is a backwards incompatible change if the function
was used improperly. See :ref:`upgrade-to-3.2` for more information.
- Removed the `invoked_subcommands` attribute largely. It is not possible
to provide it to work error free due to how the parsing works so this
API has been deprecated. See :ref:`upgrade-to-3.2` for more information.
- Restored the functionality of `invoked_subcommand` which was broken as
a regression in 3.1.
Version 3.1
-----------
(bugfix release, released on August 13th 2014)
- Fixed a regression that caused contexts of subcommands to be
created before the parent command was invoked which was a
regression from earlier Click versions.
Version 3.0
-----------
(codename "clonk clonk", released on August 12th 2014)
- formatter now no longer attempts to accomodate for terminals
smaller than 50 characters. If that happens it just assumes
a minimal width.
- added a way to not swallow exceptions in the test system.
- added better support for colors with pagers and ways to
override the autodetection.
- the CLI runner's result object now has a traceback attached.
- improved automatic short help detection to work better with
dots that do not terminate sentences.
- when definining options without actual valid option strings
now, Click will give an error message instead of silently
passing. This should catch situations where users wanted to
created arguments instead of options.
- Restructured Click internally to support vendoring.
- Added support for multi command chaining.
- Added support for defaults on options with ``multiple`` and
options and arguments with ``nargs != 1``.
- label passed to ``progressbar`` is no longer rendered with
whitespace stripped.
- added a way to disable the standalone mode of the ``main``
method on a Click command to be able to handle errors better.
- added support for returning values from command callbacks.
- added simplifications for printing to stderr from ``echo``.
- added result callbacks for groups.
- entering a context multiple times defers the cleanup until
the last exit occurs.
- added ``open_file``.
Version 2.6
-----------
(bugfix release, released on August 11th 2014)
- Fixed an issue where the wrapped streams on Python 3 would be reporting
incorrect values for seekable.
Version 2.5
-----------
(bugfix release, released on July 28th 2014)
- Fixed a bug with text wrapping on Python 3.
Version 2.4
-----------
(bugfix release, released on July 4th 2014)
- Corrected a bug in the change of the help option in 2.3.
Version 2.3
-----------
(bugfix release, released on July 3rd 2014)
- Fixed an incorrectly formatted help record for count options.
- Add support for ansi code stripping on Windows if colorama
is not available.
- restored the Click 1.0 handling of the help parameter for certain
edge cases.
Version 2.2
-----------
(bugfix release, released on June 26th 2014)
- fixed tty detection on PyPy.
- fixed an issue that progress bars were not rendered when the
context manager was entered.
Version 2.1
-----------
(bugfix release, released on June 14th 2014)
- fixed the :func:`launch` function on windows.
- improved the colorama support on windows to try hard to not
screw up the console if the application is interrupted.
- fixed windows terminals incorrectly being reported to be 80
characters wide instead of 79
- use colorama win32 bindings if available to get the correct
dimensions of a windows terminal.
- fixed an issue with custom function types on Python 3.
- fixed an issue with unknown options being incorrectly reported
in error messages.
Version 2.0
-----------
(codename "tap tap tap", released on June 6th 2014)
- added support for opening stdin/stdout on Windows in
binary mode correctly.
- added support for atomic writes to files by going through
a temporary file.
- introduced :exc:`BadParameter` which can be used to easily perform
custom validation with the same error messages as in the type system.
- added :func:`progressbar`; a function to show progress bars.
- added :func:`get_app_dir`; a function to calculate the home folder
for configs.
- Added transparent handling for ANSI codes into the :func:`echo`
function through ``colorama``.
- Added :func:`clear` function.
- Breaking change: parameter callbacks now get the parameter object
passed as second argument. There is legacy support for old callbacks
which will warn but still execute the script.
- Added :func:`style`, :func:`unstyle` and :func:`secho` for ANSI
styles.
- Added an :func:`edit` function that invokes the default editor.
- Added an :func:`launch` function that launches browsers and applications.
- nargs of -1 for arguments can now be forced to be a single item through
the required flag. It defaults to not required.
- setting a default for arguments now implicitly makes it non required.
- changed "yN" / "Yn" to "y/N" and "Y/n" in confirmation prompts.
- added basic support for bash completion.
- added :func:`getchar` to fetch a single character from the terminal.
- errors now go to stderr as intended.
- fixed various issues with more exotic parameter formats like DOS/Windows
style arguments.
- added :func:`pause` which works similar to the Windows ``pause`` cmd
built-in but becomes an automatic noop if the application is not run
through a terminal.
- added a bit of extra information about missing choice parameters.
- changed how the help function is implemented to allow global overriding
of the help option.
- added support for token normalization to implement case insensitive handling.
- added support for providing defaults for context settings.
Version 1.1
-----------
(bugfix release, released on May 23rd 2014)
- fixed a bug that caused text files in Python 2 to not accept
native strings.
Version 1.0
-----------
(no codename, released on May 21st 2014)
- Initial release.

61
CONTRIBUTING.rst Normal file
View file

@ -0,0 +1,61 @@
==========================
How to contribute to Click
==========================
Thanks for considering contributing to Click.
Support questions
=================
Please, don't use the issue tracker for this. Check whether the `Pocoo IRC
channel <http://www.pocoo.org/irc/>`_ can help with your issue. If your problem
is not strictly Click-specific, ``#python`` on Freenode is generally more
active. `StackOverflow <https://stackoverflow.com/>`_ is also worth
considering.
Reporting issues
================
- Under which versions of Python does this happen? This is even more important
if your issue is encoding related.
- Under which versions of Click does this happen? Check if this issue is fixed
in the repository.
Submitting patches
==================
- Include tests if your patch is supposed to solve a bug, and explain clearly
under which circumstances the bug happens. Make sure the test fails without
your patch.
- Try to follow `PEP8 <http://legacy.python.org/dev/peps/pep-0008/>`_, but you
may ignore the line-length-limit if following it would make the code uglier.
- For features: Consider whether your feature would be a better fit for an
`external package <http://click.pocoo.org/contrib/>`_
- For bugfixes: Submit against the latest maintenance branch instead of master!
Running the testsuite
---------------------
You probably want to set up a `virtualenv
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
The minimal requirement for running the testsuite is ``py.test``. You can
install it with::
pip install pytest
Then you can run the testsuite with::
py.test
For a more isolated test environment, you can also install ``tox`` instead of
``pytest``. You can install it with::
pip install tox
The ``tox`` command will then run all tests against multiple combinations of
Python versions and dependency versions.

View file

@ -1,12 +1,9 @@
include Makefile CHANGES LICENSE
recursive-include artwork *
recursive-include tests *
recursive-include examples *
recursive-include docs *
recursive-exclude docs *.pyc
recursive-exclude docs *.pyo
recursive-exclude tests *.pyc
recursive-exclude tests *.pyo
recursive-exclude examples *.pyc
recursive-exclude examples *.pyo
graft artwork
graft tests
graft examples
graft docs
prune docs/_build
global-exclude *.py[co] .DS_Store

View file

@ -1,5 +1,5 @@
test:
@cd tests; PYTHONPATH=.. py.test --tb=short
@cd tests; PYTHONPATH=.. pytest --tb=short
upload-docs:
$(MAKE) -C docs dirhtml

View file

@ -1,13 +0,0 @@
Metadata-Version: 1.1
Name: click
Version: 6.7
Summary: A simple wrapper around optparse for powerful command line utilities.
Home-page: http://github.com/mitsuhiko/click
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3

20
README
View file

@ -1,20 +0,0 @@
$ click_
Click is a Python package for creating beautiful command line interfaces
in a composable way with as little code as necessary. It's the "Command
Line Interface Creation Kit". It's highly configurable but comes with
sensible defaults out of the box.
It aims to make the process of writing command line tools quick and fun
while also preventing any frustration caused by the inability to implement
an intended CLI API.
Click in three points:
- arbitrary nesting of commands
- automatic help page generation
- supports lazy loading of subcommands at runtime
Read the docs at http://click.pocoo.org/
This library is stable and active. Feedback is always welcome!

91
README.rst Normal file
View file

@ -0,0 +1,91 @@
\$ click\_
==========
Click is a Python package for creating beautiful command line interfaces
in a composable way with as little code as necessary. It's the "Command
Line Interface Creation Kit". It's highly configurable but comes with
sensible defaults out of the box.
It aims to make the process of writing command line tools quick and fun
while also preventing any frustration caused by the inability to
implement an intended CLI API.
Click in three points:
- arbitrary nesting of commands
- automatic help page generation
- supports lazy loading of subcommands at runtime
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install click
Click supports Python 3.4 and newer, Python 2.7, and PyPy.
.. _pip: https://pip.pypa.io/en/stable/quickstart/
A Simple Example
----------------
What does it look like? Here is an example of a simple Click program:
.. code-block:: python
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()
And what it looks like when run:
.. code-block:: text
$ python hello.py --count=3
Your name: John
Hello John!
Hello John!
Hello John!
Donate
------
The Pallets organization develops and supports Flask and the libraries
it uses. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
* Website: https://palletsprojects.com/p/click/
* Documentation: https://click.palletsprojects.com/
* License: `BSD <https://github.com/pallets/click/blob/master/LICENSE>`_
* Releases: https://pypi.org/project/click/
* Code: https://github.com/pallets/click
* Issue tracker: https://github.com/pallets/click/issues
* Test status:
* Linux, Mac: https://travis-ci.org/pallets/click
* Windows: https://ci.appveyor.com/project/pallets/click
* Test coverage: https://codecov.io/gh/pallets/click

View file

@ -1,13 +0,0 @@
Metadata-Version: 1.1
Name: click
Version: 6.7
Summary: A simple wrapper around optparse for powerful command line utilities.
Home-page: http://github.com/mitsuhiko/click
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3

View file

@ -1,114 +0,0 @@
CHANGES
LICENSE
MANIFEST.in
Makefile
README
setup.cfg
setup.py
artwork/logo.svg
click/__init__.py
click/_bashcomplete.py
click/_compat.py
click/_termui_impl.py
click/_textwrap.py
click/_unicodefun.py
click/_winconsole.py
click/core.py
click/decorators.py
click/exceptions.py
click/formatting.py
click/globals.py
click/parser.py
click/termui.py
click/testing.py
click/types.py
click/utils.py
click.egg-info/PKG-INFO
click.egg-info/SOURCES.txt
click.egg-info/dependency_links.txt
click.egg-info/top_level.txt
docs/Makefile
docs/advanced.rst
docs/api.rst
docs/arguments.rst
docs/bashcomplete.rst
docs/changelog.rst
docs/clickdoctools.py
docs/commands.rst
docs/complex.rst
docs/conf.py
docs/contrib.rst
docs/documentation.rst
docs/exceptions.rst
docs/index.rst
docs/license.rst
docs/make.bat
docs/options.rst
docs/parameters.rst
docs/prompts.rst
docs/python3.rst
docs/quickstart.rst
docs/setuptools.rst
docs/testing.rst
docs/upgrading.rst
docs/utils.rst
docs/why.rst
docs/wincmd.rst
docs/_static/click-small.png
docs/_static/click-small@2x.png
docs/_static/click.png
docs/_static/click@2x.png
docs/_templates/sidebarintro.html
docs/_templates/sidebarlogo.html
examples/README
examples/aliases/README
examples/aliases/aliases.ini
examples/aliases/aliases.py
examples/aliases/setup.py
examples/colors/README
examples/colors/colors.py
examples/colors/setup.py
examples/complex/README
examples/complex/setup.py
examples/complex/complex/__init__.py
examples/complex/complex/cli.py
examples/complex/complex/commands/__init__.py
examples/complex/complex/commands/cmd_init.py
examples/complex/complex/commands/cmd_status.py
examples/imagepipe/.gitignore
examples/imagepipe/README
examples/imagepipe/example01.jpg
examples/imagepipe/example02.jpg
examples/imagepipe/imagepipe.py
examples/imagepipe/setup.py
examples/inout/README
examples/inout/inout.py
examples/inout/setup.py
examples/naval/README
examples/naval/naval.py
examples/naval/setup.py
examples/repo/README
examples/repo/repo.py
examples/repo/setup.py
examples/termui/README
examples/termui/setup.py
examples/termui/termui.py
examples/validation/README
examples/validation/setup.py
examples/validation/validation.py
tests/conftest.py
tests/test_arguments.py
tests/test_bashcomplete.py
tests/test_basic.py
tests/test_chain.py
tests/test_commands.py
tests/test_compat.py
tests/test_context.py
tests/test_defaults.py
tests/test_formatting.py
tests/test_imports.py
tests/test_normalization.py
tests/test_options.py
tests/test_termui.py
tests/test_testing.py
tests/test_utils.py

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@
click

View file

@ -28,7 +28,7 @@ from .decorators import pass_context, pass_obj, make_pass_decorator, \
# Types
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange
# Utilities
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
@ -66,7 +66,7 @@ __all__ = [
# Types
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', 'FloatRange',
# Utilities
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
@ -95,4 +95,4 @@ __all__ = [
disable_unicode_literals_warning = False
__version__ = '6.7'
__version__ = '7.0-dev'

View file

@ -1,27 +1,63 @@
import collections
import copy
import os
import re
from .utils import echo
from .parser import split_arg_string
from .core import MultiCommand, Option
from .core import MultiCommand, Option, Argument
from .types import Choice
WORDBREAK = '='
COMPLETION_SCRIPT = '''
COMPLETION_SCRIPT_BASH = '''
%(complete_func)s() {
local IFS=$'\n'
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
COMP_CWORD=$COMP_CWORD \\
%(autocomplete_var)s=complete $1 ) )
return 0
}
complete -F %(complete_func)s -o default %(script_names)s
complete -F %(complete_func)s %(script_names)s
'''
COMPLETION_SCRIPT_ZSH = '''
%(complete_func)s() {
local -a completions
local -a completions_with_descriptions
local -a response
response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\
COMP_CWORD=$((CURRENT-1)) \\
%(autocomplete_var)s=\"complete_zsh\" \\
%(script_names)s )}")
for key descr in ${(kv)response}; do
if [[ "$descr" == "_" ]]; then
completions+=("$key")
else
completions_with_descriptions+=("$key":"$descr")
fi
done
if [ -n "$completions_with_descriptions" ]; then
_describe '' completions_with_descriptions
fi
if [ -n "$completions" ]; then
compadd -M 'r:|=* l:|=* r:|=*' -a completions
fi
}
compdef %(complete_func)s %(script_names)s
'''
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
def get_completion_script(prog_name, complete_var):
def get_completion_script(prog_name, complete_var, shell):
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
return (COMPLETION_SCRIPT % {
script = COMPLETION_SCRIPT_ZSH if shell == 'zsh' else COMPLETION_SCRIPT_BASH
return (script % {
'complete_func': '_%s_completion' % cf_name,
'script_names': prog_name,
'autocomplete_var': complete_var,
@ -29,37 +65,169 @@ def get_completion_script(prog_name, complete_var):
def resolve_ctx(cli, prog_name, args):
"""
Parse into a hierarchy of contexts. Contexts are connected through the parent variable.
:param cli: command definition
:param prog_name: the program that is running
:param args: full list of args
:return: the final context/command parsed
"""
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
a = ctx.protected_args + ctx.args
cmd = ctx.command.get_command(ctx, a[0])
if cmd is None:
return None
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
args_remaining = ctx.protected_args + ctx.args
while ctx is not None and args_remaining:
if isinstance(ctx.command, MultiCommand):
cmd = ctx.command.get_command(ctx, args_remaining[0])
if cmd is None:
return None
ctx = cmd.make_context(
args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
args_remaining = ctx.protected_args + ctx.args
else:
ctx = ctx.parent
return ctx
def start_of_option(param_str):
"""
:param param_str: param_str to check
:return: whether or not this is the start of an option declaration (i.e. starts "-" or "--")
"""
return param_str and param_str[:1] == '-'
def is_incomplete_option(all_args, cmd_param):
"""
:param all_args: the full original list of args supplied
:param cmd_param: the current command paramter
:return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and
corresponds to this cmd_param. In other words whether this cmd_param option can still accept
values
"""
if not isinstance(cmd_param, Option):
return False
if cmd_param.is_flag:
return False
last_option = None
for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])):
if index + 1 > cmd_param.nargs:
break
if start_of_option(arg_str):
last_option = arg_str
return True if last_option and last_option in cmd_param.opts else False
def is_incomplete_argument(current_params, cmd_param):
"""
:param current_params: the current params and values for this argument as already entered
:param cmd_param: the current command parameter
:return: whether or not the last argument is incomplete and corresponds to this cmd_param. In
other words whether or not the this cmd_param argument can still accept values
"""
if not isinstance(cmd_param, Argument):
return False
current_param_values = current_params[cmd_param.name]
if current_param_values is None:
return True
if cmd_param.nargs == -1:
return True
if isinstance(current_param_values, collections.Iterable) \
and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs:
return True
return False
def get_user_autocompletions(ctx, args, incomplete, cmd_param):
"""
:param ctx: context associated with the parsed command
:param args: full list of args
:param incomplete: the incomplete text to autocomplete
:param cmd_param: command definition
:return: all the possible user-specified completions for the param
"""
results = []
if isinstance(cmd_param.type, Choice):
# Choices don't support descriptions.
results = [(c, None)
for c in cmd_param.type.choices if c.startswith(incomplete)]
elif cmd_param.autocompletion is not None:
dynamic_completions = cmd_param.autocompletion(ctx=ctx,
args=args,
incomplete=incomplete)
results = [c if isinstance(c, tuple) else (c, None)
for c in dynamic_completions]
return results
def add_subcommand_completions(ctx, incomplete, completions_out):
# Add subcommand completions.
if isinstance(ctx.command, MultiCommand):
completions_out.extend(
[(c, ctx.command.get_command(ctx, c).get_short_help_str()) for c in ctx.command.list_commands(ctx) if c.startswith(incomplete)])
# Walk up the context list and add any other completion possibilities from chained commands
while ctx.parent is not None:
ctx = ctx.parent
if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
remaining_commands = sorted(
set(ctx.command.list_commands(ctx)) - set(ctx.protected_args))
completions_out.extend(
[(c, ctx.command.get_command(ctx, c).get_short_help_str()) for c in remaining_commands if c.startswith(incomplete)])
def get_choices(cli, prog_name, args, incomplete):
"""
:param cli: command definition
:param prog_name: the program that is running
:param args: full list of args
:param incomplete: the incomplete text to autocomplete
:return: all the possible completions for the incomplete
"""
all_args = copy.deepcopy(args)
ctx = resolve_ctx(cli, prog_name, args)
if ctx is None:
return
return []
choices = []
if incomplete and not incomplete[:1].isalnum():
# In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
# without the '='
if start_of_option(incomplete) and WORDBREAK in incomplete:
partition_incomplete = incomplete.partition(WORDBREAK)
all_args.append(partition_incomplete[0])
incomplete = partition_incomplete[2]
elif incomplete == WORDBREAK:
incomplete = ''
completions = []
if start_of_option(incomplete):
# completions for partial options
for param in ctx.command.params:
if not isinstance(param, Option):
continue
choices.extend(param.opts)
choices.extend(param.secondary_opts)
elif isinstance(ctx.command, MultiCommand):
choices.extend(ctx.command.list_commands(ctx))
if isinstance(param, Option):
param_opts = [param_opt for param_opt in param.opts +
param.secondary_opts if param_opt not in all_args or param.multiple]
completions.extend(
[(o, param.help) for o in param_opts if o.startswith(incomplete)])
return completions
# completion for option values from user supplied values
for param in ctx.command.params:
if is_incomplete_option(all_args, param):
return get_user_autocompletions(ctx, all_args, incomplete, param)
# completion for argument values from user supplied values
for param in ctx.command.params:
if is_incomplete_argument(ctx.params, param):
completions.extend(get_user_autocompletions(
ctx, all_args, incomplete, param))
# Stop looking for other completions only if this argument is required.
if param.required:
return completions
break
for item in choices:
if item.startswith(incomplete):
yield item
add_subcommand_completions(ctx, incomplete, completions)
return completions
def do_complete(cli, prog_name):
def do_complete(cli, prog_name, include_descriptions):
cwords = split_arg_string(os.environ['COMP_WORDS'])
cword = int(os.environ['COMP_CWORD'])
args = cwords[1:cword]
@ -69,15 +237,19 @@ def do_complete(cli, prog_name):
incomplete = ''
for item in get_choices(cli, prog_name, args, incomplete):
echo(item)
echo(item[0])
if include_descriptions:
# ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present.
echo(item[1] if item[1] else '_')
return True
def bashcomplete(cli, prog_name, complete_var, complete_instr):
if complete_instr == 'source':
echo(get_completion_script(prog_name, complete_var))
if complete_instr.startswith('source'):
shell = 'zsh' if complete_instr == 'source_zsh' else 'bash'
echo(get_completion_script(prog_name, complete_var, shell))
return True
elif complete_instr == 'complete':
return do_complete(cli, prog_name)
elif complete_instr == 'complete' or complete_instr == 'complete_zsh':
return do_complete(cli, prog_name, complete_instr == 'complete_zsh')
return False

View file

@ -7,24 +7,31 @@ from weakref import WeakKeyDictionary
PY2 = sys.version_info[0] == 2
WIN = sys.platform.startswith('win')
CYGWIN = sys.platform.startswith('cygwin')
# Determine local App Engine environment, per Google's own suggestion
APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and
'Development/' in os.environ['SERVER_SOFTWARE'])
WIN = sys.platform.startswith('win') and not APP_ENGINE
DEFAULT_COLUMNS = 80
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])')
def get_filesystem_encoding():
return sys.getfilesystemencoding() or sys.getdefaultencoding()
def _make_text_stream(stream, encoding, errors):
def _make_text_stream(stream, encoding, errors,
force_readable=False, force_writable=False):
if encoding is None:
encoding = get_best_encoding(stream)
if errors is None:
errors = 'replace'
return _NonClosingTextIOWrapper(stream, encoding, errors,
line_buffering=True)
line_buffering=True,
force_readable=force_readable,
force_writable=force_writable)
def is_ascii_encoding(encoding):
@ -45,8 +52,10 @@ def get_best_encoding(stream):
class _NonClosingTextIOWrapper(io.TextIOWrapper):
def __init__(self, stream, encoding, errors, **extra):
self._stream = stream = _FixupStream(stream)
def __init__(self, stream, encoding, errors,
force_readable=False, force_writable=False, **extra):
self._stream = stream = _FixupStream(stream, force_readable,
force_writable)
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
# The io module is a place where the Python 3 text behavior
@ -81,10 +90,16 @@ class _FixupStream(object):
"""The new io interface needs more from streams than streams
traditionally implement. As such, this fix-up code is necessary in
some circumstances.
The forcing of readable and writable flags are there because some tools
put badly patched objects on sys (one such offender are certain version
of jupyter notebook).
"""
def __init__(self, stream):
def __init__(self, stream, force_readable=False, force_writable=False):
self._stream = stream
self._force_readable = force_readable
self._force_writable = force_writable
def __getattr__(self, name):
return getattr(self._stream, name)
@ -101,6 +116,8 @@ class _FixupStream(object):
return self._stream.read(size)
def readable(self):
if self._force_readable:
return True
x = getattr(self._stream, 'readable', None)
if x is not None:
return x()
@ -111,6 +128,8 @@ class _FixupStream(object):
return True
def writable(self):
if self._force_writable:
return True
x = getattr(self._stream, 'writable', None)
if x is not None:
return x()
@ -139,6 +158,7 @@ if PY2:
bytes = str
raw_input = raw_input
string_types = (str, unicode)
int_types = (int, long)
iteritems = lambda x: x.iteritems()
range_type = xrange
@ -165,10 +185,13 @@ if PY2:
# available (which is why we use try-catch instead of the WIN variable
# here), such as the Google App Engine development server on Windows. In
# those cases there is just nothing we can do.
def set_binary_mode(f):
return f
try:
import msvcrt
except ImportError:
set_binary_mode = lambda x: x
pass
else:
def set_binary_mode(f):
try:
@ -179,6 +202,21 @@ if PY2:
msvcrt.setmode(fileno, os.O_BINARY)
return f
try:
import fcntl
except ImportError:
pass
else:
def set_binary_mode(f):
try:
fileno = f.fileno()
except Exception:
pass
else:
flags = fcntl.fcntl(fileno, fcntl.F_GETFL)
fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
return f
def isidentifier(x):
return _identifier_re.search(x) is not None
@ -195,19 +233,22 @@ if PY2:
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
if rv is not None:
return rv
return _make_text_stream(sys.stdin, encoding, errors)
return _make_text_stream(sys.stdin, encoding, errors,
force_readable=True)
def get_text_stdout(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
if rv is not None:
return rv
return _make_text_stream(sys.stdout, encoding, errors)
return _make_text_stream(sys.stdout, encoding, errors,
force_writable=True)
def get_text_stderr(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
if rv is not None:
return rv
return _make_text_stream(sys.stderr, encoding, errors)
return _make_text_stream(sys.stderr, encoding, errors,
force_writable=True)
def filename_to_ui(value):
if isinstance(value, bytes):
@ -218,6 +259,7 @@ else:
text_type = str
raw_input = input
string_types = (str,)
int_types = (int,)
range_type = range
isidentifier = lambda x: x.isidentifier()
iteritems = lambda x: iter(x.items())
@ -298,7 +340,8 @@ else:
return False
def _force_correct_text_reader(text_reader, encoding, errors):
def _force_correct_text_reader(text_reader, encoding, errors,
force_readable=False):
if _is_binary_reader(text_reader, False):
binary_reader = text_reader
else:
@ -324,9 +367,11 @@ else:
# we're so fundamentally fucked that nothing can repair it.
if errors is None:
errors = 'replace'
return _make_text_stream(binary_reader, encoding, errors)
return _make_text_stream(binary_reader, encoding, errors,
force_readable=force_readable)
def _force_correct_text_writer(text_writer, encoding, errors):
def _force_correct_text_writer(text_writer, encoding, errors,
force_writable=False):
if _is_binary_writer(text_writer, False):
binary_writer = text_writer
else:
@ -352,7 +397,8 @@ else:
# we're so fundamentally fucked that nothing can repair it.
if errors is None:
errors = 'replace'
return _make_text_stream(binary_writer, encoding, errors)
return _make_text_stream(binary_writer, encoding, errors,
force_writable=force_writable)
def get_binary_stdin():
reader = _find_binary_reader(sys.stdin)
@ -379,19 +425,22 @@ else:
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
if rv is not None:
return rv
return _force_correct_text_reader(sys.stdin, encoding, errors)
return _force_correct_text_reader(sys.stdin, encoding, errors,
force_readable=True)
def get_text_stdout(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
if rv is not None:
return rv
return _force_correct_text_writer(sys.stdout, encoding, errors)
return _force_correct_text_writer(sys.stdout, encoding, errors,
force_writable=True)
def get_text_stderr(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
if rv is not None:
return rv
return _force_correct_text_writer(sys.stderr, encoding, errors)
return _force_correct_text_writer(sys.stderr, encoding, errors,
force_writable=True)
def filename_to_ui(value):
if isinstance(value, bytes):
@ -420,7 +469,7 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
# Standard streams first. These are simple because they don't need
# special handling for the atomic flag. It's entirely ignored.
if filename == '-':
if 'w' in mode:
if any(m in mode for m in ['w', 'a', 'x']):
if 'b' in mode:
return get_binary_stdout(), False
return get_text_stdout(encoding=encoding, errors=errors), False
@ -460,7 +509,7 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
else:
f = os.fdopen(fd, mode)
return _AtomicFile(f, tmp_filename, filename), True
return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True
# Used in a destructor call, needs extra protection from interpreter cleanup.

View file

@ -13,8 +13,10 @@ import os
import sys
import time
import math
import contextlib
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
open_stream, strip_ansi, term_len, get_best_encoding, WIN
open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \
CYGWIN
from .utils import echo
from .exceptions import ClickException
@ -41,7 +43,7 @@ def _length_hint(obj):
except TypeError:
return None
if hint is NotImplemented or \
not isinstance(hint, (int, long)) or \
not isinstance(hint, int_types) or \
hint < 0:
return None
return hint
@ -88,6 +90,7 @@ class ProgressBar(object):
self.current_item = None
self.is_hidden = not isatty(self.file)
self._last_line = None
self.short_limit = 0.5
def __enter__(self):
self.entered = True
@ -101,10 +104,13 @@ class ProgressBar(object):
if not self.entered:
raise RuntimeError('You need to use progress bars in a with block.')
self.render_progress()
return self
return self.generator()
def is_fast(self):
return time.time() - self.start <= self.short_limit
def render_finish(self):
if self.is_hidden:
if self.is_hidden or self.is_fast():
return
self.file.write(AFTER_BAR)
self.file.flush()
@ -129,13 +135,13 @@ class ProgressBar(object):
def format_eta(self):
if self.eta_known:
t = self.eta + 1
t = int(self.eta)
seconds = t % 60
t /= 60
t //= 60
minutes = t % 60
t /= 60
t //= 60
hours = t % 24
t /= 24
t //= 24
if t > 0:
days = t
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
@ -152,25 +158,27 @@ class ProgressBar(object):
def format_pct(self):
return ('% 4d%%' % int(self.pct * 100))[1:]
def format_progress_line(self):
show_percent = self.show_percent
info_bits = []
def format_bar(self):
if self.length_known:
bar_length = int(self.pct * self.width)
bar = self.fill_char * bar_length
bar += self.empty_char * (self.width - bar_length)
if show_percent is None:
show_percent = not self.show_pos
elif self.finished:
bar = self.fill_char * self.width
else:
if self.finished:
bar = self.fill_char * self.width
else:
bar = list(self.empty_char * (self.width or 1))
if self.time_per_iteration != 0:
bar[int((math.cos(self.pos * self.time_per_iteration)
/ 2.0 + 0.5) * self.width)] = self.fill_char
bar = ''.join(bar)
bar = list(self.empty_char * (self.width or 1))
if self.time_per_iteration != 0:
bar[int((math.cos(self.pos * self.time_per_iteration)
/ 2.0 + 0.5) * self.width)] = self.fill_char
bar = ''.join(bar)
return bar
def format_progress_line(self):
show_percent = self.show_percent
info_bits = []
if self.length_known and show_percent is None:
show_percent = not self.show_pos
if self.show_pos:
info_bits.append(self.format_pos())
@ -185,49 +193,47 @@ class ProgressBar(object):
return (self.bar_template % {
'label': self.label,
'bar': bar,
'bar': self.format_bar(),
'info': self.info_sep.join(info_bits)
}).rstrip()
def render_progress(self):
from .termui import get_terminal_size
nl = False
if self.is_hidden:
buf = [self.label]
nl = True
else:
buf = []
# Update width in case the terminal has been resized
if self.autowidth:
old_width = self.width
self.width = 0
clutter_length = term_len(self.format_progress_line())
new_width = max(0, get_terminal_size()[0] - clutter_length)
if new_width < old_width:
buf.append(BEFORE_BAR)
buf.append(' ' * self.max_width)
self.max_width = new_width
self.width = new_width
return
clear_width = self.width
if self.max_width is not None:
clear_width = self.max_width
buf = []
# Update width in case the terminal has been resized
if self.autowidth:
old_width = self.width
self.width = 0
clutter_length = term_len(self.format_progress_line())
new_width = max(0, get_terminal_size()[0] - clutter_length)
if new_width < old_width:
buf.append(BEFORE_BAR)
buf.append(' ' * self.max_width)
self.max_width = new_width
self.width = new_width
buf.append(BEFORE_BAR)
line = self.format_progress_line()
line_len = term_len(line)
if self.max_width is None or self.max_width < line_len:
self.max_width = line_len
buf.append(line)
clear_width = self.width
if self.max_width is not None:
clear_width = self.max_width
buf.append(' ' * (clear_width - line_len))
buf.append(BEFORE_BAR)
line = self.format_progress_line()
line_len = term_len(line)
if self.max_width is None or self.max_width < line_len:
self.max_width = line_len
buf.append(line)
buf.append(' ' * (clear_width - line_len))
line = ''.join(buf)
# Render the line only if it changed.
if line != self._last_line:
if line != self._last_line and not self.is_fast():
self._last_line = line
echo(line, file=self.file, color=self.color, nl=nl)
echo(line, file=self.file, color=self.color, nl=False)
self.file.flush()
def make_step(self, n_steps):
@ -239,7 +245,16 @@ class ProgressBar(object):
return
self.last_eta = time.time()
self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)]
# self.avg is a rolling list of length <= 7 of steps where steps are
# defined as time elapsed divided by the total progress through
# self.length.
if self.pos:
step = (time.time() - self.start) / self.pos
else:
step = time.time() - self.start
self.avg = self.avg[-6:] + [step]
self.eta_known = self.length_known
@ -252,54 +267,56 @@ class ProgressBar(object):
self.current_item = None
self.finished = True
def next(self):
def generator(self):
"""
Returns a generator which yields the items added to the bar during
construction, and updates the progress bar *after* the yielded block
returns.
"""
if not self.entered:
raise RuntimeError('You need to use progress bars in a with block.')
if self.is_hidden:
return next(self.iter)
try:
rv = next(self.iter)
self.current_item = rv
except StopIteration:
for rv in self.iter:
yield rv
else:
for rv in self.iter:
self.current_item = rv
yield rv
self.update(1)
self.finish()
self.render_progress()
raise StopIteration()
else:
self.update(1)
return rv
if not PY2:
__next__ = next
del next
def pager(text, color=None):
def pager(generator, color=None):
"""Decide what method to use for paging through text."""
stdout = _default_text_stdout()
if not isatty(sys.stdin) or not isatty(stdout):
return _nullpager(stdout, text, color)
return _nullpager(stdout, generator, color)
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
if pager_cmd:
if WIN:
return _tempfilepager(text, pager_cmd, color)
return _pipepager(text, pager_cmd, color)
return _tempfilepager(generator, pager_cmd, color)
return _pipepager(generator, pager_cmd, color)
if os.environ.get('TERM') in ('dumb', 'emacs'):
return _nullpager(stdout, text, color)
return _nullpager(stdout, generator, color)
if WIN or sys.platform.startswith('os2'):
return _tempfilepager(text, 'more <', color)
return _tempfilepager(generator, 'more <', color)
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
return _pipepager(text, 'less', color)
return _pipepager(generator, 'less', color)
import tempfile
fd, filename = tempfile.mkstemp()
os.close(fd)
try:
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
return _pipepager(text, 'more', color)
return _nullpager(stdout, text, color)
return _pipepager(generator, 'more', color)
return _nullpager(stdout, generator, color)
finally:
os.unlink(filename)
def _pipepager(text, cmd, color):
def _pipepager(generator, cmd, color):
"""Page through text by feeding it to another program. Invoking a
pager through this might support colors.
"""
@ -317,17 +334,19 @@ def _pipepager(text, cmd, color):
elif 'r' in less_flags or 'R' in less_flags:
color = True
if not color:
text = strip_ansi(text)
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
env=env)
encoding = get_best_encoding(c.stdin)
try:
c.stdin.write(text.encode(encoding, 'replace'))
c.stdin.close()
for text in generator:
if not color:
text = strip_ansi(text)
c.stdin.write(text.encode(encoding, 'replace'))
except (IOError, KeyboardInterrupt):
pass
else:
c.stdin.close()
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
# search or other commands inside less).
@ -346,10 +365,12 @@ def _pipepager(text, cmd, color):
break
def _tempfilepager(text, cmd, color):
def _tempfilepager(generator, cmd, color):
"""Page through text by invoking a program on a temporary file."""
import tempfile
filename = tempfile.mktemp()
# TODO: This never terminates if the passed generator never terminates.
text = "".join(generator)
if not color:
text = strip_ansi(text)
encoding = get_best_encoding(sys.stdout)
@ -361,11 +382,12 @@ def _tempfilepager(text, cmd, color):
os.unlink(filename)
def _nullpager(stream, text, color):
def _nullpager(stream, generator, color):
"""Simply print unformatted text. This is the ultimate fallback."""
if not color:
text = strip_ansi(text)
stream.write(text)
for text in generator:
if not color:
text = strip_ansi(text)
stream.write(text)
class Editor(object):
@ -478,6 +500,14 @@ def open_url(url, wait=False, locate=False):
args = 'start %s "" "%s"' % (
wait and '/WAIT' or '', url.replace('"', ''))
return os.system(args)
elif CYGWIN:
if locate:
url = _unquote_file(url)
args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', ''))
else:
args = 'cygstart %s "%s"' % (
wait and '-w' or '', url.replace('"', ''))
return os.system(args)
try:
if locate:
@ -506,6 +536,10 @@ def _translate_ch_to_exc(ch):
if WIN:
import msvcrt
@contextlib.contextmanager
def raw_terminal():
yield
def getchar(echo):
rv = msvcrt.getch()
if echo:
@ -522,7 +556,8 @@ else:
import tty
import termios
def getchar(echo):
@contextlib.contextmanager
def raw_terminal():
if not isatty(sys.stdin):
f = open('/dev/tty')
fd = f.fileno()
@ -533,9 +568,7 @@ else:
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = os.read(fd, 32)
if echo and isatty(sys.stdout):
sys.stdout.write(ch)
yield fd
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
sys.stdout.flush()
@ -543,5 +576,11 @@ else:
f.close()
except termios.error:
pass
_translate_ch_to_exc(ch)
return ch.decode(get_best_encoding(sys.stdin), 'replace')
def getchar(echo):
with raw_terminal() as fd:
ch = os.read(fd, 32)
if echo and isatty(sys.stdout):
sys.stdout.write(ch)
_translate_ch_to_exc(ch)
return ch.decode(get_best_encoding(sys.stdin), 'replace')

View file

@ -14,6 +14,8 @@ click = sys.modules[__name__.rsplit('.', 1)[0]]
def _find_unicode_literals_frame():
import __future__
if not hasattr(sys, '_getframe'): # not all Python implementations have it
return 0
frm = sys._getframe(1)
idx = 1
while frm is not None:
@ -60,8 +62,11 @@ def _verify_python3_env():
extra = ''
if os.name == 'posix':
import subprocess
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
try:
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
except OSError:
rv = b''
good_locales = set()
has_c_utf8 = False
@ -94,7 +99,7 @@ def _verify_python3_env():
else:
extra += (
'This system lists a couple of UTF-8 supporting locales that\n'
'you can pick from. The following suitable locales where\n'
'you can pick from. The following suitable locales were\n'
'discovered: %s'
) % ', '.join(sorted(good_locales))
@ -114,5 +119,5 @@ def _verify_python3_env():
raise RuntimeError('Click will abort further execution because Python 3 '
'was configured to use ASCII as encoding for the '
'environment. Consult http://click.pocoo.org/python3/'
'environment. Consult http://click.pocoo.org/python3/ '
'for mitigation steps.' + extra)

View file

@ -15,7 +15,7 @@ import zlib
import time
import ctypes
import msvcrt
from click._compat import _NonClosingTextIOWrapper, text_type, PY2
from ._compat import _NonClosingTextIOWrapper, text_type, PY2
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
try:
@ -261,7 +261,7 @@ def _get_windows_console_stream(f, encoding, errors):
func = _stream_factories.get(f.fileno())
if func is not None:
if not PY2:
f = getattr(f, 'buffer')
f = getattr(f, 'buffer', None)
if f is None:
return None
else:

View file

@ -1,4 +1,5 @@
import errno
import inspect
import os
import sys
from contextlib import contextmanager
@ -8,13 +9,13 @@ from functools import update_wrapper
from .types import convert_type, IntRange, BOOL
from .utils import make_str, make_default_short_help, echo, get_os_args
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
MissingParameter
from .termui import prompt, confirm
MissingParameter, Exit
from .termui import prompt, confirm, style
from .formatting import HelpFormatter, join_options
from .parser import OptionParser, split_opt
from .globals import push_context, pop_context
from ._compat import PY2, isidentifier, iteritems
from ._compat import PY2, isidentifier, iteritems, string_types
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
@ -24,6 +25,24 @@ _missing = object()
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
DEPRECATED_HELP_NOTICE = ' (DEPRECATED)'
DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \
'The command %(name)s is deprecated.'
def _maybe_show_deprecated_notice(cmd):
if cmd.deprecated:
echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True)
def fast_exit(code):
"""Exit without garbage collection, this speeds up exit by about 10ms for
things like bash completion.
"""
sys.stdout.flush()
sys.stderr.flush()
os._exit(code)
def _bashcomplete(cmd, prog_name, complete_var=None):
"""Internal handler for the bash completion support."""
@ -35,7 +54,7 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
from ._bashcomplete import bashcomplete
if bashcomplete(cmd, prog_name, complete_var, complete_instr):
sys.exit(1)
fast_exit(1)
def _check_multicommand(base_command, cmd_name, cmd, register=False):
@ -50,9 +69,7 @@ def _check_multicommand(base_command, cmd_name, cmd, register=False):
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
'added as subcommand but it in itself is a '
'multi command. ("%s" is a %s within a chained '
'%s named "%s"). This restriction was supposed to '
'be lifted in 6.0 but the fix was flawed. This '
'will be fixed in Click 7.0' % (
'%s named "%s").' % (
hint, base_command.name, cmd_name,
cmd_name, cmd.__class__.__name__,
base_command.__class__.__name__,
@ -372,7 +389,7 @@ class Context(object):
@property
def meta(self):
"""This is a dictionary which is shared with all the contexts
that are nested. It exists so that click utiltiies can store some
that are nested. It exists so that click utilities can store some
state here if they need to. It is however the responsibility of
that code to manage this dictionary well.
@ -481,7 +498,7 @@ class Context(object):
def exit(self, code=0):
"""Exits the application with a given exit code."""
sys.exit(code)
raise Exit(code)
def get_usage(self):
"""Helper method to get formatted usage string for the current
@ -655,7 +672,7 @@ class BaseCommand(object):
name from ``sys.argv[0]``.
:param complete_var: the environment variable that controls the
bash completion support. The default is
``"_<prog_name>_COMPLETE"`` with prog name in
``"_<prog_name>_COMPLETE"`` with prog_name in
uppercase.
:param standalone_mode: the default behavior is to invoke the script
in standalone mode. Click will then
@ -670,7 +687,7 @@ class BaseCommand(object):
constructor. See :class:`Context` for more information.
"""
# If we are in Python 3, we will verify that the environment is
# sane at this point of reject further execution to avoid a
# sane at this point or reject further execution to avoid a
# broken script.
if not PY2:
_verify_python3_env()
@ -697,6 +714,13 @@ class BaseCommand(object):
rv = self.invoke(ctx)
if not standalone_mode:
return rv
# it's not safe to `ctx.exit(rv)` here!
# note that `rv` may actually contain data like "1" which
# has obvious effects
# more subtle case: `rv=[None, None]` can come out of
# chained commands which all returned `None` -- so it's not
# even always obvious that `rv` indicates success/failure
# by its truthiness/falsiness
ctx.exit()
except (EOFError, KeyboardInterrupt):
echo(file=sys.stderr)
@ -711,6 +735,19 @@ class BaseCommand(object):
sys.exit(1)
else:
raise
except Exit as e:
if standalone_mode:
sys.exit(e.exit_code)
else:
# in non-standalone mode, return the exit code
# note that this is only reached if `self.invoke` above raises
# an Exit explicitly -- thus bypassing the check there which
# would return its result
# the results of non-standalone execution may therefore be
# somewhat ambiguous: if there are codepaths which lead to
# `ctx.exit(1)` and to `return 1`, the caller won't be able to
# tell the difference between the two
return e.exit_code
except Abort:
if not standalone_mode:
raise
@ -743,11 +780,16 @@ class Command(BaseCommand):
shown on the command listing of the parent command.
:param add_help_option: by default each command registers a ``--help``
option. This can be disabled by this parameter.
:param hidden: hide this command from help outputs.
:param deprecated: issues a message indicating that
the command is deprecated.
"""
def __init__(self, name, context_settings=None, callback=None,
params=None, help=None, epilog=None, short_help=None,
options_metavar='[OPTIONS]', add_help_option=True):
options_metavar='[OPTIONS]', add_help_option=True,
hidden=False, deprecated=False):
BaseCommand.__init__(self, name, context_settings)
#: the callback to execute when the command fires. This might be
#: `None` in which case nothing happens.
@ -756,13 +798,17 @@ class Command(BaseCommand):
#: should show up in the help page and execute. Eager parameters
#: will automatically be handled before non eager ones.
self.params = 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
if short_help is None and help:
short_help = make_default_short_help(help)
self.short_help = short_help
self.add_help_option = add_help_option
self.hidden = hidden
self.deprecated = deprecated
def get_usage(self, ctx):
formatter = ctx.make_formatter()
@ -816,8 +862,6 @@ class Command(BaseCommand):
def make_parser(self, ctx):
"""Creates the underlying option parser for this command."""
parser = OptionParser(ctx)
parser.allow_interspersed_args = ctx.allow_interspersed_args
parser.ignore_unknown_options = ctx.ignore_unknown_options
for param in self.get_params(ctx):
param.add_to_parser(parser, ctx)
return parser
@ -830,6 +874,10 @@ class Command(BaseCommand):
self.format_help(ctx, formatter)
return formatter.getvalue().rstrip('\n')
def get_short_help_str(self, limit=45):
"""Gets short help for the command or makes it by shortening the long help string."""
return self.short_help or self.help and make_default_short_help(self.help, limit) or ''
def format_help(self, ctx, formatter):
"""Writes the help into the formatter if it exists.
@ -850,7 +898,14 @@ class Command(BaseCommand):
if self.help:
formatter.write_paragraph()
with formatter.indentation():
formatter.write_text(self.help)
help_text = self.help
if self.deprecated:
help_text += DEPRECATED_HELP_NOTICE
formatter.write_text(help_text)
elif self.deprecated:
formatter.write_paragraph()
with formatter.indentation():
formatter.write_text(DEPRECATED_HELP_NOTICE)
def format_options(self, ctx, formatter):
"""Writes all the options into the formatter if they exist."""
@ -891,6 +946,7 @@ class Command(BaseCommand):
"""Given a context, this invokes the attached callback (if it exists)
in the right way.
"""
_maybe_show_deprecated_notice(self)
if self.callback is not None:
return ctx.invoke(self.callback, **ctx.params)
@ -996,19 +1052,29 @@ class MultiCommand(Command):
"""Extra format methods for multi methods that adds all the commands
after the options.
"""
rows = []
commands = []
for subcommand in self.list_commands(ctx):
cmd = self.get_command(ctx, subcommand)
# What is this, the tool lied about a command. Ignore it
if cmd is None:
continue
if cmd.hidden:
continue
help = cmd.short_help or ''
rows.append((subcommand, help))
commands.append((subcommand, cmd))
if rows:
with formatter.section('Commands'):
formatter.write_dl(rows)
# allow for 3 times the default spacing
if len(commands):
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
rows = []
for subcommand, cmd in commands:
help = cmd.get_short_help_str(limit)
rows.append((subcommand, help))
if rows:
with formatter.section('Commands'):
formatter.write_dl(rows)
def parse_args(self, ctx, args):
if not args and self.no_args_is_help and not ctx.resilient_parsing:
@ -1216,7 +1282,7 @@ class CommandCollection(MultiCommand):
class Parameter(object):
"""A parameter to a command comes in two versions: they are either
r"""A parameter to a command comes in two versions: they are either
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
not supported by design as some of the internals for parsing are
intentionally not finalized.
@ -1261,7 +1327,8 @@ class Parameter(object):
def __init__(self, param_decls=None, type=None, required=False,
default=None, callback=None, nargs=None, metavar=None,
expose_value=True, is_eager=False, envvar=None):
expose_value=True, is_eager=False, envvar=None,
autocompletion=None):
self.name, self.opts, self.secondary_opts = \
self._parse_decls(param_decls or (), expose_value)
@ -1284,6 +1351,7 @@ class Parameter(object):
self.is_eager = is_eager
self.metavar = metavar
self.envvar = envvar
self.autocompletion = autocompletion
@property
def human_readable_name(self):
@ -1314,12 +1382,13 @@ class Parameter(object):
def add_to_parser(self, parser, ctx):
pass
def consume_value(self, ctx, opts):
value = opts.get(self.name)
if value is None:
value = ctx.lookup_default(self.name)
if value is None:
value = self.value_from_envvar(ctx)
if value is None:
value = ctx.lookup_default(self.name)
return value
def type_cast_value(self, ctx, value):
@ -1416,6 +1485,13 @@ class Parameter(object):
def get_usage_pieces(self, ctx):
return []
def get_error_hint(self, ctx):
"""Get a stringified version of the param for use in error messages to
indicate which param caused the error.
"""
hint_list = self.opts or [self.human_readable_name]
return ' / '.join('"%s"' % x for x in hint_list)
class Option(Parameter):
"""Options are usually optional values on the command line and
@ -1424,10 +1500,15 @@ 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.
:param prompt: if set to `True` or a non empty string then the user will
be prompted for input if not set. If set to `True` the
prompt will be the option name capitalized.
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 confirmation_prompt: if set then the value will need to be confirmed
if it was prompted for.
:param hide_input: if this is `True` then the input on the prompt will be
@ -1448,6 +1529,7 @@ class Option(Parameter):
variable in case a prefix is defined on the
context.
:param help: the help string.
:param hidden: hide this option from help outputs.
"""
param_type_name = 'option'
@ -1455,7 +1537,8 @@ class Option(Parameter):
prompt=False, confirmation_prompt=False,
hide_input=False, is_flag=None, flag_value=None,
multiple=False, count=False, allow_from_autoenv=True,
type=None, help=None, **attrs):
type=None, help=None, hidden=False, show_choices=True,
show_envvar=False, **attrs):
default_is_missing = attrs.get('default', _missing) is _missing
Parameter.__init__(self, param_decls, type=type, **attrs)
@ -1468,6 +1551,7 @@ class Option(Parameter):
self.prompt = prompt_text
self.confirmation_prompt = confirmation_prompt
self.hide_input = hide_input
self.hidden = hidden
# Flags
if is_flag is None:
@ -1500,6 +1584,8 @@ class Option(Parameter):
self.allow_from_autoenv = allow_from_autoenv
self.help = help
self.show_default = show_default
self.show_choices = show_choices
self.show_envvar = show_envvar
# Sanity check for stuff we don't support
if __debug__:
@ -1548,8 +1634,8 @@ class Option(Parameter):
opts.append(decl)
if name is None and possible_names:
possible_names.sort(key=lambda x: len(x[0]))
name = possible_names[-1][1].replace('-', '_').lower()
possible_names.sort(key=lambda x: -len(x[0])) # group long options first
name = possible_names[0][1].replace('-', '_').lower()
if not isidentifier(name):
name = None
@ -1595,6 +1681,8 @@ class Option(Parameter):
parser.add_option(self.opts, **kwargs)
def get_help_record(self, ctx):
if self.hidden:
return
any_prefix_is_slash = []
def _write_opts(opts):
@ -1611,11 +1699,28 @@ class Option(Parameter):
help = self.help or ''
extra = []
if self.show_envvar:
envvar = self.envvar
if envvar is None:
if self.allow_from_autoenv and \
ctx.auto_envvar_prefix is not None:
envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper())
if envvar is not None:
extra.append('env var: %s' % (
', '.join('%s' % d for d in envvar)
if isinstance(envvar, (list, tuple))
else envvar, ))
if self.default is not None and self.show_default:
extra.append('default: %s' % (
', '.join('%s' % d for d in self.default)
if isinstance(self.default, (list, tuple))
else self.default, ))
if isinstance(self.show_default, string_types):
default_string = '({})'.format(self.show_default)
elif isinstance(self.default, (list, tuple)):
default_string = ', '.join('%s' % d for d in self.default)
elif inspect.isfunction(self.default):
default_string = "(dynamic)"
else:
default_string = self.default
extra.append('default: {}'.format(default_string))
if self.required:
extra.append('required')
if extra:
@ -1649,8 +1754,8 @@ class Option(Parameter):
if self.is_bool_flag:
return confirm(self.prompt, default)
return prompt(self.prompt, default=default,
hide_input=self.hide_input,
return prompt(self.prompt, default=default, type=self.type,
hide_input=self.hide_input, show_choices=self.show_choices,
confirmation_prompt=self.confirmation_prompt,
value_proc=lambda x: self.process_value(ctx, x))
@ -1710,7 +1815,9 @@ class Argument(Parameter):
def make_metavar(self):
if self.metavar is not None:
return self.metavar
var = self.name.upper()
var = self.type.get_metavar(self)
if not var:
var = self.name.upper()
if not self.required:
var = '[%s]' % var
if self.nargs != 1:
@ -1735,6 +1842,9 @@ class Argument(Parameter):
def get_usage_pieces(self, ctx):
return [self.make_metavar()]
def get_error_hint(self, ctx):
return '"%s"' % self.make_metavar()
def add_to_parser(self, parser, ctx):
parser.add_argument(dest=self.name, nargs=self.nargs,
obj=self)

View file

@ -61,7 +61,7 @@ def make_pass_decorator(object_type, ensure=False):
raise RuntimeError('Managed to invoke callback without a '
'context object of type %r existing'
% object_type.__name__)
return ctx.invoke(f, obj, *args[1:], **kwargs)
return ctx.invoke(f, obj, *args, **kwargs)
return update_wrapper(new_func, f)
return decorator
@ -85,12 +85,12 @@ def _make_command(f, name, attrs, cls):
help = inspect.cleandoc(help)
attrs['help'] = help
_check_for_unicode_literals()
return cls(name=name or f.__name__.lower(),
return cls(name=name or f.__name__.lower().replace('_', '-'),
callback=f, params=params, **attrs)
def command(name=None, cls=None, **attrs):
"""Creates a new :class:`Command` and uses the decorated function as
r"""Creates a new :class:`Command` and uses the decorated function as
callback. This will also automatically attach all decorated
:func:`option`\s and :func:`argument`\s as parameters to the command.
@ -105,7 +105,7 @@ def command(name=None, cls=None, **attrs):
command :class:`Group`.
:param name: the name of the command. This defaults to the function
name.
name with underscores replaced by dashes.
:param cls: the command class to instantiate. This defaults to
:class:`Command`.
"""
@ -164,10 +164,13 @@ def option(*param_decls, **attrs):
:class:`Option`.
"""
def decorator(f):
if 'help' in attrs:
attrs['help'] = inspect.cleandoc(attrs['help'])
OptionClass = attrs.pop('cls', Option)
_param_memo(f, OptionClass(param_decls, **attrs))
# 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)
_param_memo(f, OptionClass(param_decls, **option_attrs))
return f
return decorator
@ -235,7 +238,11 @@ def version_option(version=None, *param_decls, **attrs):
:param others: everything else is forwarded to :func:`option`.
"""
if version is None:
module = sys._getframe(1).f_globals.get('__name__')
if hasattr(sys, '_getframe'):
module = sys._getframe(1).f_globals.get('__name__')
else:
module = ''
def decorator(f):
prog_name = attrs.pop('prog_name', None)
message = attrs.pop('message', '%(prog)s, version %(version)s')

View file

@ -2,6 +2,12 @@ from ._compat import PY2, filename_to_ui, get_text_stderr
from .utils import echo
def _join_param_hints(param_hint):
if isinstance(param_hint, (tuple, list)):
return ' / '.join('"%s"' % x for x in param_hint)
return param_hint
class ClickException(Exception):
"""An exception that Click can handle and show to the user."""
@ -9,15 +15,25 @@ class ClickException(Exception):
exit_code = 1
def __init__(self, message):
ctor_msg = message
if PY2:
if message is not None:
message = message.encode('utf-8')
Exception.__init__(self, message)
if ctor_msg is not None:
ctor_msg = ctor_msg.encode('utf-8')
Exception.__init__(self, ctor_msg)
self.message = message
def format_message(self):
return self.message
def __str__(self):
return self.message
if PY2:
__unicode__ = __str__
def __str__(self):
return self.message.encode('utf-8')
def show(self, file=None):
if file is None:
file = get_text_stderr()
@ -37,14 +53,20 @@ class UsageError(ClickException):
def __init__(self, message, ctx=None):
ClickException.__init__(self, message)
self.ctx = ctx
self.cmd = self.ctx and self.ctx.command or None
def show(self, file=None):
if file is None:
file = get_text_stderr()
color = None
hint = ''
if (self.cmd is not None and
self.cmd.get_help_option(self.ctx) is not None):
hint = ('Try "%s %s" for help.\n'
% (self.ctx.command_path, self.ctx.help_option_names[0]))
if self.ctx is not None:
color = self.ctx.color
echo(self.ctx.get_usage() + '\n', file=file, color=color)
echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color)
echo('Error: %s' % self.format_message(), file=file, color=color)
@ -76,11 +98,11 @@ class BadParameter(UsageError):
if self.param_hint is not None:
param_hint = self.param_hint
elif self.param is not None:
param_hint = self.param.opts or [self.param.human_readable_name]
param_hint = self.param.get_error_hint(self.ctx)
else:
return 'Invalid value: %s' % self.message
if isinstance(param_hint, (tuple, list)):
param_hint = ' / '.join('"%s"' % x for x in param_hint)
param_hint = _join_param_hints(param_hint)
return 'Invalid value for %s: %s' % (param_hint, self.message)
@ -105,11 +127,10 @@ class MissingParameter(BadParameter):
if self.param_hint is not None:
param_hint = self.param_hint
elif self.param is not None:
param_hint = self.param.opts or [self.param.human_readable_name]
param_hint = self.param.get_error_hint(self.ctx)
else:
param_hint = None
if isinstance(param_hint, (tuple, list)):
param_hint = ' / '.join('"%s"' % x for x in param_hint)
param_hint = _join_param_hints(param_hint)
param_type = self.param_type
if param_type is None and self.param is not None:
@ -164,10 +185,13 @@ class BadOptionUsage(UsageError):
for an option is not correct.
.. versionadded:: 4.0
:param option_name: the name of the option being used incorrectly.
"""
def __init__(self, message, ctx=None):
def __init__(self, option_name, message, ctx=None):
UsageError.__init__(self, message, ctx)
self.option_name = option_name
class BadArgumentUsage(UsageError):
@ -199,3 +223,13 @@ class FileError(ClickException):
class Abort(RuntimeError):
"""An internal signalling exception that signals Click to abort."""
class Exit(RuntimeError):
"""An exception that indicates that the application should exit with some
status code.
:param code: the status code to exit with.
"""
def __init__(self, code=0):
self.exit_code = code

View file

@ -9,7 +9,7 @@ def get_current_context(silent=False):
access the current context object from anywhere. This is a more implicit
alternative to the :func:`pass_context` decorator. This function is
primarily useful for helpers such as :func:`echo` which might be
interested in changing it's behavior based on the current context.
interested in changing its behavior based on the current context.
To push the current context, :meth:`Context.scope` can be used.

View file

@ -74,8 +74,8 @@ def _unpack_args(args, nargs_spec):
def _error_opt_args(nargs, opt):
if nargs == 1:
raise BadOptionUsage('%s option requires an argument' % opt)
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
def split_opt(opt):
@ -342,7 +342,7 @@ class OptionParser(object):
del state.rargs[:nargs]
elif explicit_value is not None:
raise BadOptionUsage('%s option does not take a value' % opt)
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
else:
value = None

View file

@ -1,12 +1,14 @@
import os
import sys
import struct
import inspect
import itertools
from ._compat import raw_input, text_type, string_types, \
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
from .utils import echo
from .exceptions import Abort, UsageError
from .types import convert_type
from .types import convert_type, Choice, Path
from .globals import resolve_color_default
@ -14,8 +16,25 @@ from .globals import resolve_color_default
# functions to customize how they work.
visible_prompt_func = raw_input
_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta',
'cyan', 'white', 'reset')
_ansi_colors = {
'black': 30,
'red': 31,
'green': 32,
'yellow': 33,
'blue': 34,
'magenta': 35,
'cyan': 36,
'white': 37,
'reset': 39,
'bright_black': 90,
'bright_red': 91,
'bright_green': 92,
'bright_yellow': 93,
'bright_blue': 94,
'bright_magenta': 95,
'bright_cyan': 96,
'bright_white': 97,
}
_ansi_reset_all = '\033[0m'
@ -24,23 +43,27 @@ def hidden_prompt_func(prompt):
return getpass.getpass(prompt)
def _build_prompt(text, suffix, show_default=False, default=None):
def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None):
prompt = text
if type is not None and show_choices and isinstance(type, Choice):
prompt += ' (' + ", ".join(map(str, type.choices)) + ')'
if default is not None and show_default:
prompt = '%s [%s]' % (prompt, default)
return prompt + suffix
def prompt(text, default=None, hide_input=False,
confirmation_prompt=False, type=None,
value_proc=None, prompt_suffix=': ',
show_default=True, err=False):
def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
type=None, value_proc=None, prompt_suffix=': ', show_default=True,
err=False, show_choices=True):
"""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
function will catch it and raise a :exc:`Abort` exception.
.. versionadded:: 7.0
Added the show_choices parameter.
.. versionadded:: 6.0
Added unicode support for cmd.exe on Windows.
@ -61,6 +84,10 @@ def prompt(text, default=None, hide_input=False,
:param show_default: shows or hides the default value in the prompt.
:param err: if set to true the file defaults to ``stderr`` instead of
``stdout``, the same as with echo.
:param show_choices: Show or hide choices if the passed type is a Choice.
For example if type is a Choice of either day or week,
show_choices is true and text is "Group by" then the
prompt will be "Group by (day, week): ".
"""
result = None
@ -82,17 +109,18 @@ def prompt(text, default=None, hide_input=False,
if value_proc is None:
value_proc = convert_type(type, default)
prompt = _build_prompt(text, prompt_suffix, show_default, default)
prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type)
while 1:
while 1:
value = prompt_func(prompt)
if value:
break
# If a default is set and used, then the confirmation
# prompt is always skipped because that's the only thing
# that really makes sense.
elif default is not None:
if isinstance(value_proc, Path):
# validate Path default value(exists, dir_okay etc.)
value = default
break
return default
try:
result = value_proc(value)
@ -166,8 +194,14 @@ def get_terminal_size():
sz = shutil_get_terminal_size()
return sz.columns, sz.lines
# We provide a sensible default for get_winterm_size() when being invoked
# inside a subprocess. Without this, it would not provide a useful input.
if get_winterm_size is not None:
return get_winterm_size()
size = get_winterm_size()
if size == (0, 0):
return (79, 24)
else:
return size
def ioctl_gwinsz(fd):
try:
@ -195,22 +229,33 @@ def get_terminal_size():
return int(cr[1]), int(cr[0])
def echo_via_pager(text, color=None):
def echo_via_pager(text_or_generator, color=None):
"""This function takes a text and shows it via an environment specific
pager on stdout.
.. versionchanged:: 3.0
Added the `color` flag.
:param text: the text to page.
:param text_or_generator: the text to page, or alternatively, a
generator emitting the text to page.
:param color: controls if the pager supports ANSI colors or not. The
default is autodetection.
"""
color = resolve_color_default(color)
if not isinstance(text, string_types):
text = text_type(text)
if inspect.isgeneratorfunction(text_or_generator):
i = text_or_generator()
elif isinstance(text_or_generator, string_types):
i = [text_or_generator]
else:
i = iter(text_or_generator)
# convert every element of i to a text type if necessary
text_generator = (el if isinstance(el, string_types) else text_type(el)
for el in i)
from ._termui_impl import pager
return pager(text + '\n', color)
return pager(itertools.chain(text_generator, "\n"), color)
def progressbar(iterable=None, length=None, label=None, show_eta=True,
@ -347,10 +392,21 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
* ``magenta``
* ``cyan``
* ``white`` (might be light gray)
* ``bright_black``
* ``bright_red``
* ``bright_green``
* ``bright_yellow``
* ``bright_blue``
* ``bright_magenta``
* ``bright_cyan``
* ``bright_white``
* ``reset`` (reset the color code only)
.. versionadded:: 2.0
.. versionadded:: 7.0
Added support for bright colors.
:param text: the string to style with ansi codes.
:param fg: if provided this will become the foreground color.
:param bg: if provided this will become the background color.
@ -369,13 +425,13 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
bits = []
if fg:
try:
bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30))
except ValueError:
bits.append('\033[%dm' % (_ansi_colors[fg]))
except KeyError:
raise TypeError('Unknown color %r' % fg)
if bg:
try:
bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40))
except ValueError:
bits.append('\033[%dm' % (_ansi_colors[bg] + 10))
except KeyError:
raise TypeError('Unknown color %r' % bg)
if bold is not None:
bits.append('\033[%dm' % (1 if bold else 22))
@ -405,7 +461,7 @@ def unstyle(text):
return strip_ansi(text)
def secho(text, file=None, nl=True, err=False, color=None, **styles):
def secho(message=None, file=None, nl=True, err=False, color=None, **styles):
"""This function combines :func:`echo` and :func:`style` into one
call. As such the following two calls are the same::
@ -417,7 +473,9 @@ def secho(text, file=None, nl=True, err=False, color=None, **styles):
.. versionadded:: 2.0
"""
return echo(style(text, **styles), file=file, nl=nl, err=err, color=color)
if message is not None:
message = style(message, **styles)
return echo(message, file=file, nl=nl, err=err, color=color)
def edit(text=None, editor=None, env=None, require_save=True,
@ -510,6 +568,11 @@ def getchar(echo=False):
return f(echo)
def raw_terminal():
from ._termui_impl import raw_terminal as f
return f()
def pause(info='Press any key to continue ...', err=False):
"""This command stops execution and waits for the user to press any
key to continue. This is similar to the Windows batch "pause"

View file

@ -3,8 +3,9 @@ import sys
import shutil
import tempfile
import contextlib
import shlex
from ._compat import iteritems, PY2
from ._compat import iteritems, PY2, string_types
# If someone wants to vendor click, we want to ensure the
@ -72,27 +73,44 @@ def make_input_stream(input, charset):
class Result(object):
"""Holds the captured result of an invoked CLI script."""
def __init__(self, runner, output_bytes, exit_code, exception,
exc_info=None):
def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code,
exception, exc_info=None):
#: The runner that created the result
self.runner = runner
#: The output as bytes.
self.output_bytes = output_bytes
#: The standard output as bytes.
self.stdout_bytes = stdout_bytes
#: The standard error as bytes, or False(y) if not available
self.stderr_bytes = stderr_bytes
#: The exit code as integer.
self.exit_code = exit_code
#: The exception that happend if one did.
#: The exception that happened if one did.
self.exception = exception
#: The traceback
self.exc_info = exc_info
@property
def output(self):
"""The output as unicode string."""
return self.output_bytes.decode(self.runner.charset, 'replace') \
"""The (standard) output as unicode string."""
return self.stdout
@property
def stdout(self):
"""The standard output as unicode string."""
return self.stdout_bytes.decode(self.runner.charset, 'replace') \
.replace('\r\n', '\n')
@property
def stderr(self):
"""The standard error as unicode string."""
if not self.stderr_bytes:
raise ValueError("stderr not separately captured")
return self.stderr_bytes.decode(self.runner.charset, 'replace') \
.replace('\r\n', '\n')
def __repr__(self):
return '<Result %s>' % (
return '<%s %s>' % (
type(self).__name__,
self.exception and repr(self.exception) or 'okay',
)
@ -111,14 +129,21 @@ class CliRunner(object):
to stdout. This is useful for showing examples in
some circumstances. Note that regular prompts
will automatically echo the input.
:param mix_stderr: if this is set to `False`, then stdout and stderr are
preserved as independent streams. This is useful for
Unix-philosophy apps that have predictable stdout and
noisy stderr, such that each may be measured
independently
"""
def __init__(self, charset=None, env=None, echo_stdin=False):
def __init__(self, charset=None, env=None, echo_stdin=False,
mix_stderr=True):
if charset is None:
charset = 'utf-8'
self.charset = charset
self.env = env or {}
self.echo_stdin = echo_stdin
self.mix_stderr = mix_stderr
def get_default_prog_name(self, cli):
"""Given a command object it will return the default program name
@ -163,16 +188,27 @@ class CliRunner(object):
env = self.make_env(env)
if PY2:
sys.stdout = sys.stderr = bytes_output = StringIO()
bytes_output = StringIO()
if self.echo_stdin:
input = EchoingStdin(input, bytes_output)
sys.stdout = bytes_output
if not self.mix_stderr:
bytes_error = StringIO()
sys.stderr = bytes_error
else:
bytes_output = io.BytesIO()
if self.echo_stdin:
input = EchoingStdin(input, bytes_output)
input = io.TextIOWrapper(input, encoding=self.charset)
sys.stdout = sys.stderr = io.TextIOWrapper(
sys.stdout = io.TextIOWrapper(
bytes_output, encoding=self.charset)
if not self.mix_stderr:
bytes_error = io.BytesIO()
sys.stderr = io.TextIOWrapper(
bytes_error, encoding=self.charset)
if self.mix_stderr:
sys.stderr = sys.stdout
sys.stdin = input
@ -196,6 +232,7 @@ class CliRunner(object):
return char
default_color = color
def should_strip_ansi(stream=None, color=None):
if color is None:
return not default_color
@ -221,7 +258,7 @@ class CliRunner(object):
pass
else:
os.environ[key] = value
yield bytes_output
yield (bytes_output, not self.mix_stderr and bytes_error)
finally:
for key, value in iteritems(old_env):
if value is None:
@ -241,7 +278,7 @@ class CliRunner(object):
clickpkg.formatting.FORCED_WIDTH = old_forced_width
def invoke(self, cli, args=None, input=None, env=None,
catch_exceptions=True, color=False, **extra):
catch_exceptions=True, color=False, mix_stderr=False, **extra):
"""Invokes a command in an isolated environment. The arguments are
forwarded directly to the command line script, the `extra` keyword
arguments are passed to the :meth:`~clickpkg.Command.main` function of
@ -260,7 +297,10 @@ class CliRunner(object):
The ``color`` parameter was added.
:param cli: the command to invoke
:param args: the arguments to invoke
:param args: the arguments to invoke. It may be given as an iterable
or a string. When given as string it will be interpreted
as a Unix shell command. More details at
:func:`shlex.split`.
:param input: the input data for `sys.stdin`.
:param env: the environment overrides.
:param catch_exceptions: Whether to catch any other exceptions than
@ -270,36 +310,48 @@ class CliRunner(object):
application can still override this explicitly.
"""
exc_info = None
with self.isolation(input=input, env=env, color=color) as out:
with self.isolation(input=input, env=env, color=color) as outstreams:
exception = None
exit_code = 0
if isinstance(args, string_types):
args = shlex.split(args)
try:
cli.main(args=args or (),
prog_name=self.get_default_prog_name(cli), **extra)
prog_name = extra.pop("prog_name")
except KeyError:
prog_name = self.get_default_prog_name(cli)
try:
cli.main(args=args or (), prog_name=prog_name, **extra)
except SystemExit as e:
if e.code != 0:
exc_info = sys.exc_info()
exit_code = e.code
if exit_code is None:
exit_code = 0
if exit_code != 0:
exception = e
exc_info = sys.exc_info()
exit_code = e.code
if not isinstance(exit_code, int):
sys.stdout.write(str(exit_code))
sys.stdout.write('\n')
exit_code = 1
except Exception as e:
if not catch_exceptions:
raise
exception = e
exit_code = -1
exit_code = 1
exc_info = sys.exc_info()
finally:
sys.stdout.flush()
output = out.getvalue()
stdout = outstreams[0].getvalue()
stderr = outstreams[1] and outstreams[1].getvalue()
return Result(runner=self,
output_bytes=output,
stdout_bytes=stdout,
stderr_bytes=stderr,
exit_code=exit_code,
exception=exception,
exc_info=exc_info)

View file

@ -126,34 +126,54 @@ class StringParamType(ParamType):
class Choice(ParamType):
"""The choice type allows a value to be checked against a fixed set of
supported values. All of these values have to be strings.
"""The choice type allows a value to be checked against a fixed set
of supported values. All of these values have to be strings.
You should only pass a list or tuple of choices. Other iterables
(like generators) may lead to surprising results.
See :ref:`choice-opts` for an example.
:param case_sensitive: Set to false to make choices case
insensitive. Defaults to true.
"""
name = 'choice'
def __init__(self, choices):
def __init__(self, choices, case_sensitive=True):
self.choices = choices
self.case_sensitive = case_sensitive
def get_metavar(self, param):
return '[%s]' % '|'.join(self.choices)
def get_missing_message(self, param):
return 'Choose from %s.' % ', '.join(self.choices)
return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices)
def convert(self, value, param, ctx):
# Exact match
if value in self.choices:
return value
# Match through normalization
# Match through normalization and case sensitivity
# first do token_normalize_func, then lowercase
# preserve original `value` to produce an accurate message in
# `self.fail`
normed_value = value
normed_choices = self.choices
if ctx is not None and \
ctx.token_normalize_func is not None:
value = ctx.token_normalize_func(value)
for choice in self.choices:
if ctx.token_normalize_func(choice) == value:
return choice
normed_value = ctx.token_normalize_func(value)
normed_choices = [ctx.token_normalize_func(choice) for choice in
self.choices]
if not self.case_sensitive:
normed_value = normed_value.lower()
normed_choices = [choice.lower() for choice in normed_choices]
if normed_value in normed_choices:
return normed_value
self.fail('invalid choice: %s. (choose from %s)' %
(value, ', '.join(self.choices)), param, ctx)
@ -214,23 +234,6 @@ class IntRange(IntParamType):
return 'IntRange(%r, %r)' % (self.min, self.max)
class BoolParamType(ParamType):
name = 'boolean'
def convert(self, value, param, ctx):
if isinstance(value, bool):
return bool(value)
value = value.lower()
if value in ('true', '1', 'yes', 'y'):
return True
elif value in ('false', '0', 'no', 'n'):
return False
self.fail('%s is not a valid boolean' % value, param, ctx)
def __repr__(self):
return 'BOOL'
class FloatParamType(ParamType):
name = 'float'
@ -245,6 +248,62 @@ class FloatParamType(ParamType):
return 'FLOAT'
class FloatRange(FloatParamType):
"""A parameter that works similar to :data:`click.FLOAT` but restricts
the value to fit into a range. The default behavior is to fail if the
value falls outside the range, but it can also be silently clamped
between the two edges.
See :ref:`ranges` for an example.
"""
name = 'float range'
def __init__(self, min=None, max=None, clamp=False):
self.min = min
self.max = max
self.clamp = clamp
def convert(self, value, param, ctx):
rv = FloatParamType.convert(self, value, param, ctx)
if self.clamp:
if self.min is not None and rv < self.min:
return self.min
if self.max is not None and rv > self.max:
return self.max
if self.min is not None and rv < self.min or \
self.max is not None and rv > self.max:
if self.min is None:
self.fail('%s is bigger than the maximum valid value '
'%s.' % (rv, self.max), param, ctx)
elif self.max is None:
self.fail('%s is smaller than the minimum valid value '
'%s.' % (rv, self.min), param, ctx)
else:
self.fail('%s is not in the valid range of %s to %s.'
% (rv, self.min, self.max), param, ctx)
return rv
def __repr__(self):
return 'FloatRange(%r, %r)' % (self.min, self.max)
class BoolParamType(ParamType):
name = 'boolean'
def convert(self, value, param, ctx):
if isinstance(value, bool):
return bool(value)
value = value.lower()
if value in ('true', 't', '1', 'yes', 'y'):
return True
elif value in ('false', 'f', '0', 'no', 'n'):
return False
self.fail('%s is not a valid boolean' % value, param, ctx)
def __repr__(self):
return 'BOOL'
class UUIDParameterType(ParamType):
name = 'uuid'
@ -273,9 +332,12 @@ class File(ParamType):
opened in binary mode or for writing. The encoding parameter can be used
to force a specific encoding.
The `lazy` flag controls if the file should be opened immediately or
upon first IO. The default is to be non lazy for standard input and
output streams as well as files opened for reading, lazy otherwise.
The `lazy` flag controls if the file should be opened immediately or upon
first IO. The default is to be non-lazy for standard input and output
streams as well as files opened for reading, `lazy` otherwise. When opening a
file lazily for reading, it is still opened temporarily for validation, but
will not be held open until first IO. lazy is mainly useful when opening
for writing to avoid creating the file until it is needed.
Starting with Click 2.0, files can also be opened atomically in which
case all writes go into a separate file in the same folder and upon
@ -358,14 +420,16 @@ class Path(ParamType):
: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.
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 type: optionally a string type that should be used to
represent the path. The default is `None` which
means the return value will be either bytes or
unicode depending on what makes most sense given the
input data Click deals with.
:param path_type: optionally a string type that should be used to
represent the path. The default is `None` which
means the return value will be either bytes or
unicode depending on what makes most sense given the
input data Click deals with.
"""
envvar_list_splitter = os.path.pathsep
@ -384,7 +448,7 @@ class Path(ParamType):
if self.file_okay and not self.dir_okay:
self.name = 'file'
self.path_type = 'File'
if self.dir_okay and not self.file_okay:
elif self.dir_okay and not self.file_okay:
self.name = 'directory'
self.path_type = 'Directory'
else:

View file

@ -43,6 +43,7 @@ def make_str(value):
def make_default_short_help(help, max_length=45):
"""Return a condensed version of help string."""
words = help.split()
total_length = 0
result = []
@ -171,7 +172,7 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
Primarily it means that you can print binary data as well as Unicode
data on both 2.x and 3.x to the given file in the most appropriate way
possible. This is a very carefree function as in that it will try its
possible. This is a very carefree function in that it will try its
best to not fail. As of Click 6.0 this includes support for unicode
output on the Windows console.
@ -183,7 +184,7 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
- hide ANSI codes automatically if the destination file is not a
terminal.
.. _colorama: http://pypi.python.org/pypi/colorama
.. _colorama: https://pypi.org/project/colorama/
.. versionchanged:: 6.0
As of Click 6.0 the echo function will properly support unicode

View file

@ -1,130 +1,20 @@
# Makefile for Sphinx documentation
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
SPHINXPROJ = Jinja
SOURCEDIR = .
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
# Put it first so that "make" without argument is like "make help".
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
clean:
-rm -rf $(BUILDDIR)/*
.PHONY: help Makefile
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Classy.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Classy.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Classy"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Classy"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
latexpdf: latex
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

BIN
docs/_static/click-icon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

BIN
docs/_static/click.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -1,13 +0,0 @@
<h3>About</h3>
<p>
Click is a Python package for creating beautiful command line interfaces in a
composable way with as little amount of code as necessary. Its the “Command
Line Interface Creation Kit”.
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="http://click.pocoo.org/">The Click Website</a></li>
<li><a href="http://pypi.python.org/pypi/click">click @ PyPI</a></li>
<li><a href="http://github.com/mitsuhiko/click">click @ github</a></li>
<li><a href="http://github.com/mitsuhiko/click/issues">Issue Tracker</a></li>
</ul>

View file

@ -1,3 +0,0 @@
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/click-small@2x.png', 1) }}" width="120" height="50" alt="Logo">
</a></p>

View file

@ -277,7 +277,7 @@ options:
If you go with this solution, the extra arguments will be collected in
:attr:`Context.args`.
2. You can attach a :func:`argument` with ``nargs`` set to `-1` which
will eat up all leftover arguments. In this case it's recommeded to
will eat up all leftover arguments. In this case it's recommended to
set the `type` to :data:`UNPROCESSED` to avoid any string processing
on those arguments as otherwise they are forced into unicode strings
automatically which is often not what you want.
@ -295,8 +295,8 @@ In the end you end up with something like this:
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode')
@click.argument('timeit_args', nargs=-1, type=click.UNPROCESSED)
def cli(verbose, timeit_args):
"""A wrapper around Python's timeit."""
cmdline = ['python', '-mtimeit'] + list(timeit_args)
"""A fake wrapper around Python's timeit."""
cmdline = ['echo', 'python', '-mtimeit'] + list(timeit_args)
if verbose:
click.echo('Invoking: %s' % ' '.join(cmdline))
call(cmdline)
@ -321,7 +321,7 @@ are important to know about how this ignoring of unhandled flag happens:
generally end up like that. Note that because the parser cannot know
if an option will accept an argument or not, the ``bar`` part might be
handled as an argument.
* Unknown short options might be partially handled and reassmebled if
* Unknown short options might be partially handled and reassembled if
necessary. For instance in the above example there is an option
called ``-v`` which enables verbose mode. If the command would be
ignored with ``-va`` then the ``-v`` part would be handled by Click
@ -346,7 +346,7 @@ Global Context Access
.. versionadded:: 5.0
Starting with Click 5.0 it is possible to access the current context from
anywhere within the same through through the use of the
anywhere within the same thread through the use of the
:func:`get_current_context` function which returns it. This is primarily
useful for accessing the context bound object as well as some flags that
are stored on it to customize the runtime behavior. For instance the

View file

@ -243,3 +243,21 @@ And from the command line:
.. click:run::
invoke(touch, ['--', '-foo.txt', 'bar.txt'])
If you don't like the ``--`` marker, you can set ignore_unknown_options to
True to avoid checking unknown options:
.. click:example::
@click.command(context_settings={"ignore_unknown_options": True})
@click.argument('files', nargs=-1, type=click.Path())
def touch(files):
for filename in files:
click.echo(filename)
And from the command line:
.. click:run::
invoke(touch, ['-foo.txt', 'bar.txt'])

View file

@ -12,17 +12,15 @@ Limitations
Bash completion is only available if a script has been installed properly,
and not executed through the ``python`` command. For information about
how to do that, see :ref:`setuptools-integration`. Also, Click currently
only supports completion for Bash.
Currently, Bash completion is an internal feature that is not customizable.
This might be relaxed in future versions.
how to do that, see :ref:`setuptools-integration`. Click currently
only supports completion for Bash and Zsh.
What it Completes
-----------------
Generally, the Bash completion support will complete subcommands and
parameters. Subcommands are always listed whereas parameters only if at
Generally, the Bash completion support will complete subcommands, options
and any option or argument values where the type is click.Choice.
Subcommands and choices are always listed whereas options only if at
least a dash has been provided. Example::
$ repo <TAB><TAB>
@ -30,6 +28,60 @@ least a dash has been provided. Example::
$ repo clone -<TAB><TAB>
--deep --help --rev --shallow -r
Additionally, custom suggestions can be provided for arguments and options with
the ``autocompletion`` parameter. ``autocompletion`` should a callback function
that returns a list of strings. This is useful when the suggestions need to be
dynamically generated at bash completion time. The callback function will be
passed 3 keyword arguments:
- ``ctx`` - The current click context.
- ``args`` - The list of arguments passed in.
- ``incomplete`` - The partial word that is being completed, as a string. May
be an empty string ``''`` if no characters have been entered yet.
Here is an example of using a callback function to generate dynamic suggestions:
.. click:example::
import os
def get_env_vars(ctx, args, incomplete):
return [k for k in os.environ.keys() if incomplete in k]
@click.command()
@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)
def cmd1(envvar):
click.echo('Environment variable: %s' % envvar)
click.echo('Value: %s' % os.environ[envvar])
Completion help strings (ZSH only)
----------------------------------
ZSH supports showing documentation strings for completions. These are taken
from the help parameters of options and subcommands. For dynamically generated
completions a help string can be provided by returning a tuple instead of a
string. The first element of the tuple is the completion and the second is the
help string to display.
Here is an example of using a callback function to generate dynamic suggestions with help strings:
.. click:example::
import os
def get_colors(ctx, args, incomplete):
colors = [('red', 'help string for the color red'),
('blue', 'help string for the color blue'),
('green', 'help string for the color green')]
return [c for c in colors if incomplete in c[0]]
@click.command()
@click.argument("color", type=click.STRING, autocompletion=get_colors)
def cmd1(color):
click.echo('Chosen color is %s' % color)
Activation
----------
@ -42,14 +94,18 @@ with dashes replaced by underscores.
If your tool is called ``foo-bar``, then the magic variable is called
``_FOO_BAR_COMPLETE``. By exporting it with the ``source`` value it will
spit out the activation script which can be trivally activated.
spit out the activation script which can be trivially activated.
For instance, to enable Bash completion for your ``foo-bar`` script, this
is what you would need to put into your ``.bashrc``::
eval "$(_FOO_BAR_COMPLETE=source foo-bar)"
From this point onwards, your script will have Bash completion enabled.
For zsh users add this to your ``.zshrc``::
eval "$(_FOO_BAR_COMPLETE=source_zsh foo-bar)"
From this point onwards, your script will have autocompletion enabled.
Activation Script
-----------------
@ -64,6 +120,12 @@ This can be easily accomplished::
_FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh
And then you would put this into your bashrc instead::
For zsh:
_FOO_BAR_COMPLETE=source_zsh foo-bar > foo-bar-complete.sh
And then you would put this into your .bashrc or .zshrc instead::
. /path/to/foo-bar-complete.sh

View file

@ -1,3 +1,3 @@
.. currentmodule:: click
.. include:: ../CHANGES
.. include:: ../CHANGES.rst

View file

@ -1,275 +0,0 @@
import os
import sys
import click
import shutil
import tempfile
import contextlib
import subprocess
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from docutils import nodes
from docutils.statemachine import ViewList
from sphinx.domains import Domain
from sphinx.util.compat import Directive
class EchoingStdin(object):
def __init__(self, input, output):
self._input = input
self._output = output
def __getattr__(self, x):
return getattr(self._input, x)
def _echo(self, rv):
mark = False
if rv.endswith('\xff'):
rv = rv[:-1]
mark = True
self._output.write(rv)
if mark:
self._output.write('^D\n')
return rv
def read(self, n=-1):
return self._echo(self._input.read(n))
def readline(self, n=-1):
return self._echo(self._input.readline(n))
def readlines(self):
return [self._echo(x) for x in self._input.readlines()]
def __iter__(self):
return iter(self._echo(x) for x in self._input)
@contextlib.contextmanager
def fake_modules():
old_call = subprocess.call
def dummy_call(*args, **kwargs):
with tempfile.TemporaryFile('wb+') as f:
kwargs['stdout'] = f
kwargs['stderr'] = f
rv = subprocess.Popen(*args, **kwargs).wait()
f.seek(0)
click.echo(f.read().decode('utf-8', 'replace').rstrip())
return rv
subprocess.call = dummy_call
try:
yield
finally:
subprocess.call = old_call
@contextlib.contextmanager
def isolation(input=None, env=None):
if isinstance(input, unicode):
input = input.encode('utf-8')
input = StringIO(input or '')
output = StringIO()
sys.stdin = EchoingStdin(input, output)
sys.stdin.encoding = 'utf-8'
def visible_input(prompt=None):
sys.stdout.write(prompt or '')
val = input.readline().rstrip('\r\n')
sys.stdout.write(val + '\n')
sys.stdout.flush()
return val
def hidden_input(prompt=None):
sys.stdout.write((prompt or '') + '\n')
sys.stdout.flush()
return input.readline().rstrip('\r\n')
sys.stdout = output
sys.stderr = output
old_visible_prompt_func = click.termui.visible_prompt_func
old_hidden_prompt_func = click.termui.hidden_prompt_func
click.termui.visible_prompt_func = visible_input
click.termui.hidden_prompt_func = hidden_input
old_env = {}
try:
if env:
for key, value in env.iteritems():
old_env[key] = os.environ.get(value)
os.environ[key] = value
yield output
finally:
for key, value in old_env.iteritems():
if value is None:
try:
del os.environ[key]
except Exception:
pass
else:
os.environ[key] = value
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
click.termui.visible_prompt_func = old_visible_prompt_func
click.termui.hidden_prompt_func = old_hidden_prompt_func
@contextlib.contextmanager
def isolated_filesystem():
cwd = os.getcwd()
t = tempfile.mkdtemp()
os.chdir(t)
try:
yield
finally:
os.chdir(cwd)
try:
shutil.rmtree(t)
except (OSError, IOError):
pass
class ExampleRunner(object):
def __init__(self):
self.namespace = {
'click': click,
'__file__': 'dummy.py',
}
def declare(self, source):
with fake_modules():
code = compile(source, '<docs>', 'exec')
eval(code, self.namespace)
def run(self, source):
code = compile(source, '<docs>', 'exec')
buffer = []
def invoke(cmd, args=None, prog_name=None,
input=None, terminate_input=False, env=None,
**extra):
if env:
for key, value in sorted(env.items()):
if ' ' in value:
value = '"%s"' % value
buffer.append('$ export %s=%s' % (key, value))
args = args or []
if prog_name is None:
prog_name = cmd.name.replace('_', '-')
buffer.append(('$ %s %s' % (
prog_name,
' '.join(('"%s"' % x) if ' ' in x else x for x in args)
)).rstrip())
if isinstance(input, (tuple, list)):
input = '\n'.join(input) + '\n'
if terminate_input:
input += '\xff'
with isolation(input=input, env=env) as output:
try:
cmd.main(args=args, prog_name=prog_name.split()[-1],
**extra)
except SystemExit:
pass
buffer.extend(output.getvalue().splitlines())
def println(text=''):
buffer.append(text)
eval(code, self.namespace, {
'invoke': invoke,
'println': println,
'isolated_filesystem': isolated_filesystem,
})
return buffer
def close(self):
pass
def parse_rst(state, content_offset, doc):
node = nodes.section()
# hack around title style bookkeeping
surrounding_title_styles = state.memo.title_styles
surrounding_section_level = state.memo.section_level
state.memo.title_styles = []
state.memo.section_level = 0
state.nested_parse(doc, content_offset, node, match_titles=1)
state.memo.title_styles = surrounding_title_styles
state.memo.section_level = surrounding_section_level
return node.children
def get_example_runner(document):
runner = getattr(document, 'click_example_runner', None)
if runner is None:
runner = document.click_example_runner = ExampleRunner()
return runner
class ExampleDirective(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
def run(self):
doc = ViewList()
runner = get_example_runner(self.state.document)
try:
runner.declare('\n'.join(self.content))
except:
runner.close()
raise
doc.append('.. sourcecode:: python', '')
doc.append('', '')
for line in self.content:
doc.append(' ' + line, '')
return parse_rst(self.state, self.content_offset, doc)
class RunExampleDirective(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
def run(self):
doc = ViewList()
runner = get_example_runner(self.state.document)
try:
rv = runner.run('\n'.join(self.content))
except:
runner.close()
raise
doc.append('.. sourcecode:: text', '')
doc.append('', '')
for line in rv:
doc.append(' ' + line, '')
return parse_rst(self.state, self.content_offset, doc)
class ClickDomain(Domain):
name = 'click'
label = 'Click'
directives = {
'example': ExampleDirective,
'run': RunExampleDirective,
}
def delete_example_runner_state(app, doctree):
runner = getattr(doctree, 'click_example_runner', None)
if runner is not None:
runner.close()
del doctree.click_example_runner
def setup(app):
app.add_domain(ClickDomain)
app.connect('doctree-read', delete_example_runner_state)

View file

@ -29,7 +29,7 @@ when an inner command runs:
@cli.command()
def sync():
click.echo('Synching')
click.echo('Syncing')
Here is what this looks like:
@ -87,6 +87,10 @@ script like this:
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
# ensure that ctx.obj exists and is a dict (in case `cli()` is called
# by means other than the `if` block below
ctx.ensure_object(dict)
ctx.obj['DEBUG'] = debug
@cli.command()
@ -417,7 +421,7 @@ to not use the file type and manually open the file through
For a more complex example that also improves upon handling of the
pipelines have a look at the `imagepipe multi command chaining demo
<https://github.com/mitsuhiko/click/tree/master/examples/imagepipe>`__ in
<https://github.com/pallets/click/tree/master/examples/imagepipe>`__ in
the Click repository. It implements a pipeline based image editing tool
that has a nice internal structure for the pipelines.
@ -437,7 +441,7 @@ you're not satisfied with the defaults.
The default map can be nested arbitrarily for each subcommand and
provided when the script is invoked. Alternatively, it can also be
overriden at any point by commands. For instance, a top-level command could
overridden at any point by commands. For instance, a top-level command could
load the defaults from a configuration file.
Example usage:

View file

@ -153,10 +153,10 @@ One obvious way to remedy this is to store a reference to the repo in the
plugin, but then a command needs to be aware that it's attached below such a
plugin.
There is a much better system that can built by taking advantage of the linked
nature of contexts. We know that the plugin context is linked to the context
that created our repo. Because of that, we can start a search for the last
level where the object stored by the context was a repo.
There is a much better system that can be built by taking advantage of the
linked nature of contexts. We know that the plugin context is linked to the
context that created our repo. Because of that, we can start a search for
the last level where the object stored by the context was a repo.
Built-in support for this is provided by the :func:`make_pass_decorator`
factory, which will create decorators for us that find objects (it
@ -187,8 +187,9 @@ The above example only works if there was an outer command that created a
``Repo`` object and stored it in the context. For some more advanced use
cases, this might become a problem. The default behavior of
:func:`make_pass_decorator` is to call :meth:`Context.find_object`
which will find the object. If it can't find the object, it will raise an
error. The alternative behavior is to use :meth:`Context.ensure_object`
which will find the object. If it can't find the object,
:meth:`make_pass_decorator` will raise an error.
The alternative behavior is to use :meth:`Context.ensure_object`
which will find the object, and if it cannot find it, will create one and
store it in the innermost context. This behavior can also be enabled for
:func:`make_pass_decorator` by passing ``ensure=True``:
@ -210,7 +211,7 @@ As such it runs standalone:
@click.command()
@pass_repo
def cp(repo):
click.echo(repo)
click.echo(isinstance(repo, Repo))
As you can see:

View file

@ -1,219 +1,47 @@
# -*- coding: utf-8 -*-
#
# click documentation build configuration file, created by
# sphinx-quickstart on Mon Apr 26 19:53:01 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
from pallets_sphinx_themes import ProjectLink, get_version
import sys, os
import datetime
# Project --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('..'))
sys.path.append(os.path.abspath('.'))
project = "Click"
copyright = "2014 Pallets Team"
author = "Pallets Team"
release, version = get_version("Click")
# -- General configuration -----------------------------------------------------
# General --------------------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
master_doc = "index"
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "pallets_sphinx_themes"]
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
'clickdoctools']
# HTML -----------------------------------------------------------------
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'click'
copyright = u'%d, Armin Ronacher' % datetime.datetime.utcnow().year
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
#html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme = "click"
html_theme_options = {
"index_sidebar_logo": False,
}
html_context = {
"project_links": [
ProjectLink("Donate to Pallets", "https://palletsprojects.com/donate"),
ProjectLink("Click Website", "https://palletsprojects.com/p/click/"),
ProjectLink("PyPI releases", "https://pypi.org/project/Click/"),
ProjectLink("Source Code", "https://github.com/pallets/click/"),
ProjectLink("Issue Tracker", "https://github.com/pallets/click/issues/"),
]
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = 'click'
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
'sourcelink.html', 'searchbox.html']
"index": ["project.html", "versions.html", "searchbox.html"],
"**": ["localtoc.html", "relations.html", "versions.html", "searchbox.html"],
}
singlehtml_sidebars = {"index": ["project.html", "versions.html", "localtoc.html"]}
html_static_path = ["_static"]
html_favicon = "_static/click-icon.png"
html_logo = "_static/click-logo-sidebar.png"
html_show_sourcelink = False
html_domain_indices = False
html_experimental_html5_writer = True
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# LaTeX ----------------------------------------------------------------
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'clickdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'click.tex', u'click documentation',
u'Armin Ronacher', 'manual'),
(master_doc, "Click.tex", "Click Documentation", "Pallets Team", "manual")
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'click', u'click documentation',
[u'Armin Ronacher'], 1)
]
intersphinx_mapping = {
'http://docs.python.org/dev': None
}

View file

@ -72,6 +72,41 @@ Example:
And what it looks like:
.. click:run::
invoke(cli, args=['--help'])
.. _doc-meta-variables:
Truncating Help Texts
---------------------
Click gets command help text from function docstrings. However if you
already use docstrings to document function arguments you may not want
to see :param: and :return: lines in your help text.
You can use the ``\f`` escape marker to have Click truncate the help text
after the marker.
Example:
.. click:example::
@click.command()
@click.pass_context
def cli(ctx):
"""First paragraph.
This is a very long second
paragraph and not correctly
wrapped but it will be rewrapped.
\f
:param click.core.Context ctx: Click context.
"""
And what it looks like:
.. click:run::
invoke(cli, args=['--help'])

View file

@ -1,5 +1,12 @@
Welcome to the Click Documentation
==================================
.. rst-class:: hide-header
Welcome to Click
================
.. image:: _static/click-logo.png
:align: center
:scale: 50%
:target: https://palletsprojects.com/p/click/
Click is a Python package for creating beautiful command line interfaces
in a composable way with as little code as necessary. It's the "Command
@ -50,8 +57,8 @@ You can get the library directly from PyPI::
pip install click
Documentation Contents
----------------------
Documentation
-------------
This part of the documentation guides you through all of the library's
usage patterns.

View file

@ -1,155 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
set SPHINXPROJ=Jinja
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Classy.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Classy.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

View file

@ -10,14 +10,38 @@ decorator. Since options can come in various different versions, there
are a ton of parameters to configure their behavior. Options in click are
distinct from :ref:`positional arguments <arguments>`.
Name Your Options
-----------------
The naming rules can be found in :ref:`parameter_names`. In short, you
can refer the option **implicitly** by the longest dash-prefixed argument:
.. click:example::
@click.command()
@click.option('-s', '--string-to-echo')
def echo(string_to_echo):
click.echo(string_to_echo)
Or, **explicitly**, by giving one non-dash-prefixed argument:
.. click:example::
@click.command()
@click.option('-s', '--string-to-echo', 'string')
def echo(string):
click.echo(string)
Basic Value Options
-------------------
The most basic option is a value option. These options accept one
argument which is a value. If no type is provided, the type of the default
value is used. If no default value is provided, the type is assumed to be
:data:`STRING`. By default, the name of the parameter is the first long
option defined; otherwise the first short one is used.
:data:`STRING`. Unless a name is explicitly specified, the name of the
parameter is the first long option defined; otherwise the first short one is
used. By default, options are not required, however to make an option required,
simply pass in `required=True` as an argument to the decorator.
.. click:example::
@ -26,6 +50,23 @@ option defined; otherwise the first short one is used.
def dots(n):
click.echo('.' * n)
.. click:example::
# How to make an option required
@click.command()
@click.option('--n', required=True, type=int)
def dots(n):
click.echo('.' * n)
.. click:example::
# How to use a Python reserved word such as `from` as a parameter
@click.command()
@click.option('--from', '-f', 'from_')
@click.option('--to', '-t')
def reserved_param_name(from_, to):
click.echo('from %s to %s' % (from_, to))
And on the command line:
.. click:run::
@ -35,6 +76,19 @@ And on the command line:
In this case the option is of type :data:`INT` because the default value
is an integer.
To show the default values when showing command help, use ``show_default=True``
.. click:example::
@click.command()
@click.option('--n', default=1, show_default=True)
def dots(n):
click.echo('.' * n)
.. click:run::
invoke(dots, args=['--help'])
Multi Value Options
-------------------
@ -70,7 +124,7 @@ the tuple. For this you can directly specify a tuple as type:
.. click:example::
@click.command()
@click.option('--item', type=(unicode, int))
@click.option('--item', type=(str, int))
def putitem(item):
click.echo('name=%s id=%d' % item)
@ -87,7 +141,7 @@ used. The above example is thus equivalent to this:
.. click:example::
@click.command()
@click.option('--item', nargs=2, type=click.Tuple([unicode, int]))
@click.option('--item', nargs=2, type=click.Tuple([str, int]))
def putitem(item):
click.echo('name=%s id=%d' % item)
@ -282,6 +336,11 @@ What it looks like:
println()
invoke(digest, args=['--help'])
.. note::
You should only pass the choices as list or tuple. Other iterables (like
generators) may lead to surprising results.
.. _option-prompting:
Prompting
@ -374,6 +433,21 @@ from the environment:
def hello(username):
print("Hello,", username)
To describe what the default value will be, set it in ``show_default``.
.. click:example::
@click.command()
@click.option('--username', prompt=True,
default=lambda: os.environ.get('USER', ''),
show_default='current user')
def hello(username):
print("Hello,", username)
.. click:run::
invoke(hello, args=['--help'])
Callbacks and Eager Options
---------------------------
@ -514,6 +588,33 @@ And from the command line:
invoke(greet, env={'GREETER_USERNAME': 'john'},
auto_envvar_prefix='GREETER')
When using ``auto_envvar_prefix`` with command groups, the command name needs
to be included in the environment variable, between the prefix and the parameter name, *i.e.* *PREFIX_COMMAND_VARIABLE*.
Example:
.. click:example::
@click.group()
@click.option('--debug/--no-debug')
def cli(debug):
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
@cli.command()
@click.option('--username')
def greet(username):
click.echo('Hello %s!' % username)
if __name__ == '__main__':
cli(auto_envvar_prefix='GREETER')
.. click:run::
invoke(cli, args=['greet',],
env={'GREETER_GREET_USERNAME': 'John', 'GREETER_DEBUG': 'false'},
auto_envvar_prefix='GREETER')
The second option is to manually pull values in from specific environment
variables by defining the name of the environment variable on the option.

View file

@ -47,8 +47,8 @@ different behavior and some are supported out of the box:
``bool`` / :data:`click.BOOL`:
A parameter that accepts boolean values. This is automatically used
for boolean flags. If used with string values ``1``, ``yes``, ``y``
and ``true`` convert to `True` and ``0``, ``no``, ``n`` and ``false``
for boolean flags. If used with string values ``1``, ``yes``, ``y``, ``t``
and ``true`` convert to `True` and ``0``, ``no``, ``n``, ``f`` and ``false``
convert to `False`.
:data:`click.UUID`:
@ -67,25 +67,41 @@ different behavior and some are supported out of the box:
.. autoclass:: IntRange
:noindex:
.. autoclass:: FloatRange
:noindex:
Custom parameter types can be implemented by subclassing
:class:`click.ParamType`. For simple cases, passing a Python function that
fails with a `ValueError` is also supported, though discouraged.
.. _parameter_names:
Parameter Names
---------------
Parameters (both options and arguments) accept a number of positional
arguments which are the parameter declarations. Each string with a
single dash is added as short argument; each string starting with a double
dash as long one. If a string is added without any dashes, it becomes the
internal parameter name which is also used as variable name.
Parameters (both options and arguments) accept a number of positional arguments
which are passed to the command function as parameters. Each string with a
single dash is added as a short argument; each string starting with a double
dash as a long one.
If a parameter is not given a name without dashes, a name is generated
If a string is added without any dashes, it becomes the internal parameter name
which is also used as variable name.
If all names for a parameter contain dashes, the internal name is generated
automatically by taking the longest argument and converting all dashes to
underscores. For an option with ``('-f', '--foo-bar')``, the parameter
name is `foo_bar`. For an option with ``('-x',)``, the parameter is `x`.
For an option with ``('-f', '--filename', 'dest')``, the parameter is
called `dest`.
underscores.
The internal name is converted to lowercase.
Examples:
* For an option with ``('-f', '--foo-bar')``, the parameter name is `foo_bar`.
* For an option with ``('-x',)``, the parameter is `x`.
* For an option with ``('-f', '--filename', 'dest')``, the parameter name is `dest`.
* For an option with ``('--CamelCaseOption',)``, the parameter is `camelcaseoption`.
* For an arguments with ``(`foogle`)``, the parameter name is `foogle`. To
provide a different human readable name for use in help text, see the section
about :ref:`doc-meta-variables`.
Implementing Custom Types
-------------------------

View file

@ -6,9 +6,9 @@ Python 3 Support
Click supports Python 3, but like all other command line utility libraries,
it suffers from the Unicode text model in Python 3. All examples in the
documentation were written so that they could run on both Python 2.x and
Python 3.3 or higher.
Python 3.4 or higher.
At the moment, it is strongly recommended is to use Python 2 for Click
At the moment, it is strongly recommended to use Python 2 for Click
utilities unless Python 3 is a hard requirement.
.. _python3-limitations:

View file

@ -51,7 +51,7 @@ If you are on Windows (or none of the above methods worked) you must install
Once you have it installed, run the ``pip`` command from above, but without
the `sudo` prefix.
.. _installing pip: http://pip.readthedocs.org/en/latest/installing.html
.. _installing pip: https://pip.readthedocs.io/en/latest/installing.html
Once you have virtualenv installed, just fire up a shell and create
your own environment. I usually create a project folder and a `venv`
@ -79,7 +79,7 @@ And if you want to go back to the real world, use the following command::
$ deactivate
After doing this, the prompt of your shell should be as familar as before.
After doing this, the prompt of your shell should be as familiar as before.
Now, let's move on. Enter the following command to get Click activated in your
virtualenv::
@ -102,26 +102,26 @@ Examples of Click applications can be found in the documentation as well
as in the GitHub repository together with readme files:
* ``inout``: `File input and output
<https://github.com/mitsuhiko/click/tree/master/examples/inout>`_
<https://github.com/pallets/click/tree/master/examples/inout>`_
* ``naval``: `Port of docopt naval example
<https://github.com/mitsuhiko/click/tree/master/examples/naval>`_
<https://github.com/pallets/click/tree/master/examples/naval>`_
* ``aliases``: `Command alias example
<https://github.com/mitsuhiko/click/tree/master/examples/aliases>`_
<https://github.com/pallets/click/tree/master/examples/aliases>`_
* ``repo``: `Git-/Mercurial-like command line interface
<https://github.com/mitsuhiko/click/tree/master/examples/repo>`_
<https://github.com/pallets/click/tree/master/examples/repo>`_
* ``complex``: `Complex example with plugin loading
<https://github.com/mitsuhiko/click/tree/master/examples/complex>`_
<https://github.com/pallets/click/tree/master/examples/complex>`_
* ``validation``: `Custom parameter validation example
<https://github.com/mitsuhiko/click/tree/master/examples/validation>`_
<https://github.com/pallets/click/tree/master/examples/validation>`_
* ``colors``: `Colorama ANSI color support
<https://github.com/mitsuhiko/click/tree/master/examples/colors>`_
<https://github.com/pallets/click/tree/master/examples/colors>`_
* ``termui``: `Terminal UI functions demo
<https://github.com/mitsuhiko/click/tree/master/examples/termui>`_
<https://github.com/pallets/click/tree/master/examples/termui>`_
* ``imagepipe``: `Multi command chaining demo
<https://github.com/mitsuhiko/click/tree/master/examples/imagepipe>`_
<https://github.com/pallets/click/tree/master/examples/imagepipe>`_
Basic Concepts
--------------
Basic Concepts - Creating a Command
-----------------------------------
Click is based on declaring commands through decorators. Internally, there
is a non-decorator interface for advanced use cases, but it's discouraged
@ -174,7 +174,9 @@ correction in case the terminal is misconfigured instead of dying with an
As an added benefit, starting with Click 2.0, the echo function also
has good support for ANSI colors. It will automatically strip ANSI codes
if the output stream is a file and if colorama is supported, ANSI colors
will also work on Windows. See :ref:`ansi-colors` for more information.
will also work on Windows. Note that in Python 2, the :func:`echo` function
does not parse color code information from bytearrays. See :ref:`ansi-colors`
for more information.
If you don't need this, you can also use the `print()` construct /
function.

2
docs/requirements.txt Normal file
View file

@ -0,0 +1,2 @@
Sphinx
Pallets-Sphinx-Themes

View file

@ -61,6 +61,11 @@ Example::
assert 'Debug mode is on' in result.output
assert 'Syncing' in result.output
Additional keyword arguments passed to ``.invoke()`` will be used to construct the initial Context object. For example, if you want to run your tests against a fixed terminal width you can use the following::
runner = CliRunner()
result = runner.invoke(cli, ['--debug', 'sync'], terminal_width=60)
File System Isolation
---------------------

View file

@ -44,7 +44,7 @@ properly by this function.
Multicommand Chaining API
`````````````````````````
Click 3 introduced multicommand chaning. This required a change in how
Click 3 introduced multicommand chaining. This required a change in how
Click internally dispatches. Unfortunately this change was not correctly
implemented and it appeared that it was possible to provide an API that
can inform the super command about all the subcommands that will be

View file

@ -41,7 +41,7 @@ new in Click 6.0.
Click now emulates output streams on Windows to support unicode to the
Windows console through separate APIs. For more information see
`wincmd`_.
:doc:`wincmd`.
.. versionadded:: 3.0
@ -61,7 +61,8 @@ ANSI Colors
Starting with Click 2.0, the :func:`echo` function gained extra
functionality to deal with ANSI colors and styles. Note that on Windows,
this functionality is only available if `colorama`_ is installed. If it
is installed, then ANSI codes are intelligently handled.
is installed, then ANSI codes are intelligently handled. Note that in Python
2, the echo function doesn't parse color code information from bytearrays.
Primarily this means that:
@ -95,7 +96,7 @@ a single function called :func:`secho`::
click.secho('ATTENTION', blink=True, bold=True)
.. _colorama: https://pypi.python.org/pypi/colorama
.. _colorama: https://pypi.org/project/colorama/
Pager Support
-------------
@ -114,6 +115,17 @@ Example:
click.echo_via_pager('\n'.join('Line %d' % idx
for idx in range(200)))
If you want to use the pager for a lot of text, especially if generating everything in advance would take a lot of time, you can pass a generator (or generator function) instead of a string:
.. click:example::
def _generate_output():
for idx in range(50000):
yield "Line %d\n" % idx
@click.command()
def less():
click.echo_via_pager(_generate_output())
Screen Clearing
---------------
@ -226,7 +238,7 @@ Launching Applications
.. versionadded:: 2.0
Click supports launching applications through :func:`launch`. This can be
used to open the default application assocated with a URL or filetype.
used to open the default application associated with a URL or filetype.
This can be used to launch web browsers or picture viewers, for instance.
In addition to this, it can also launch the file manager and automatically
select the provided file.
@ -266,7 +278,7 @@ streams respond to Unicode and binary data.
Because of this, click provides the :func:`get_binary_stream` and
:func:`get_text_stream` functions, which produce consistent results with
different Python versions and for a wide variety pf terminal configurations.
different Python versions and for a wide variety of terminal configurations.
The end result is that these functions will always return a functional
stream object (except in very odd cases in Python 3; see
@ -283,7 +295,7 @@ Example::
Click now emulates output streams on Windows to support unicode to the
Windows console through separate APIs. For more information see
`wincmd`_.
:doc:`wincmd`.
Intelligent File Opening

View file

@ -8,7 +8,7 @@ This question is easy to answer: because there is not a single command
line utility for Python out there which ticks the following boxes:
* is lazily composable without restrictions
* fully follows the Unix command line conventions
* supports implementation of Unix/POSIX command line conventions
* supports loading values from environment variables out of the box
* supports for prompting of custom values
* is fully nestable and composable

View file

@ -0,0 +1,12 @@
$ bashcompletion
bashcompletion is a simple example of an application that
tries to autocomplete commands, arguments and options.
This example requires Click 2.0 or higher.
Usage:
$ pip install --editable .
$ eval "$(_BASHCOMPLETION_COMPLETE=source bashcompletion)"
$ bashcompletion --help

View file

@ -0,0 +1,41 @@
import click
import os
@click.group()
def cli():
pass
def get_env_vars(ctx, args, incomplete):
for key in os.environ.keys():
if incomplete in key:
yield key
@cli.command(help='A command to print environment variables')
@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)
def cmd1(envvar):
click.echo('Environment variable: %s' % envvar)
click.echo('Value: %s' % os.environ[envvar])
@click.group(help='A group that holds a subcommand')
def group():
pass
def list_users(ctx, args, incomplete):
# Here you can generate completions dynamically
users = ['bob', 'alice']
for user in users:
if user.startswith(incomplete):
yield user
@group.command(help='Choose a user')
@click.argument("user", type=click.STRING, autocompletion=list_users)
def subcmd(user):
click.echo('Chosen user is %s' % user)
cli.add_command(group)

View file

@ -0,0 +1,15 @@
from setuptools import setup
setup(
name='click-example-bashcompletion',
version='1.0',
py_modules=['bashcompletion'],
include_package_data=True,
install_requires=[
'click',
],
entry_points='''
[console_scripts]
bashcompletion=bashcompletion:cli
''',
)

View file

@ -2,7 +2,9 @@ import click
all_colors = 'black', 'red', 'green', 'yellow', 'blue', 'magenta', \
'cyan', 'white'
'cyan', 'white', 'bright_black', 'bright_red', \
'bright_green', 'bright_yellow', 'bright_blue', \
'bright_magenta', 'bright_cyan', 'bright_white'
@click.command()

View file

@ -1,6 +1,6 @@
import click
try:
from urllib import parser as urlparse
from urllib import parse as urlparse
except ImportError:
import urlparse

View file

@ -1,8 +1,8 @@
[wheel]
universal = 1
[bdist_wheel]
universal=1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[metadata]
license_file = LICENSE
[tool:pytest]
addopts = -p no:warnings --tb=short

View file

@ -1,28 +1,57 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import re
import ast
from setuptools import setup
_version_re = re.compile(r'__version__\s+=\s+(.*)')
with open('click/__init__.py', 'rb') as f:
version = str(ast.literal_eval(_version_re.search(
f.read().decode('utf-8')).group(1)))
with io.open('README.rst', 'rt', encoding='utf8') as f:
readme = f.read()
with io.open('click/__init__.py', 'rt', encoding='utf8') as f:
version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1)
setup(
name='click',
version=version,
url='https://palletsprojects.com/p/click/',
author='Armin Ronacher',
author_email='armin.ronacher@active-4.com',
version=version,
url='http://github.com/mitsuhiko/click',
maintainer='Pallets team',
maintainer_email='contact@palletsprojects.com',
long_description=readme,
packages=['click'],
description='A simple wrapper around optparse for '
'powerful command line utilities.',
license='BSD',
extras_require={
'dev': [
'pytest>=3',
'coverage',
'tox',
'sphinx',
],
'docs': [
'sphinx',
'Pallets-Sphinx-Themes',
]
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
)

View file

@ -188,7 +188,7 @@ def test_empty_nargs(runner):
result = runner.invoke(cmd2, [])
assert result.exit_code == 2
assert 'Missing argument "arg"' in result.output
assert 'Missing argument "ARG..."' in result.output
def test_missing_arg(runner):
@ -199,7 +199,7 @@ def test_missing_arg(runner):
result = runner.invoke(cmd, [])
assert result.exit_code == 2
assert 'Missing argument "arg".' in result.output
assert 'Missing argument "ARG".' in result.output
def test_implicit_non_required(runner):

View file

@ -4,14 +4,59 @@ import click
from click._bashcomplete import get_choices
def choices_without_help(cli, args, incomplete):
completions = get_choices(cli, 'dummy', args, incomplete)
return [c[0] for c in completions]
def choices_with_help(cli, args, incomplete):
return list(get_choices(cli, 'dummy', args, incomplete))
def test_single_command():
@click.command()
@click.option('--local-opt')
def cli(local_opt):
pass
assert list(get_choices(cli, 'lol', [], '-')) == ['--local-opt']
assert list(get_choices(cli, 'lol', [], '')) == []
assert choices_without_help(cli, [], '-') == ['--local-opt']
assert choices_without_help(cli, [], '') == []
def test_boolean_flag():
@click.command()
@click.option('--shout/--no-shout', default=False)
def cli(local_opt):
pass
assert choices_without_help(cli, [], '-') == ['--shout', '--no-shout']
def test_multi_value_option():
@click.group()
@click.option('--pos', nargs=2, type=float)
def cli(local_opt):
pass
@cli.command()
@click.option('--local-opt')
def sub(local_opt):
pass
assert choices_without_help(cli, [], '-') == ['--pos']
assert choices_without_help(cli, ['--pos'], '') == []
assert choices_without_help(cli, ['--pos', '1.0'], '') == []
assert choices_without_help(cli, ['--pos', '1.0', '1.0'], '') == ['sub']
def test_multi_option():
@click.command()
@click.option('--message', '-m', multiple=True)
def cli(local_opt):
pass
assert choices_without_help(cli, [], '-') == ['--message', '-m']
assert choices_without_help(cli, ['-m'], '') == []
def test_small_chain():
@ -25,10 +70,10 @@ def test_small_chain():
def sub(local_opt):
pass
assert list(get_choices(cli, 'lol', [], '')) == ['sub']
assert list(get_choices(cli, 'lol', [], '-')) == ['--global-opt']
assert list(get_choices(cli, 'lol', ['sub'], '')) == []
assert list(get_choices(cli, 'lol', ['sub'], '-')) == ['--local-opt']
assert choices_without_help(cli, [], '') == ['sub']
assert choices_without_help(cli, [], '-') == ['--global-opt']
assert choices_without_help(cli, ['sub'], '') == []
assert choices_without_help(cli, ['sub'], '-') == ['--local-opt']
def test_long_chain():
@ -47,16 +92,206 @@ def test_long_chain():
def bsub(bsub_opt):
pass
COLORS = ['red', 'green', 'blue']
def get_colors(ctx, args, incomplete):
for c in COLORS:
if c.startswith(incomplete):
yield c
def search_colors(ctx, args, incomplete):
for c in COLORS:
if incomplete in c:
yield c
CSUB_OPT_CHOICES = ['foo', 'bar']
CSUB_CHOICES = ['bar', 'baz']
@bsub.command('csub')
@click.option('--csub-opt')
def csub(csub_opt):
@click.option('--csub-opt', type=click.Choice(CSUB_OPT_CHOICES))
@click.option('--csub', type=click.Choice(CSUB_CHOICES))
@click.option('--search-color', autocompletion=search_colors)
@click.argument('color', autocompletion=get_colors)
def csub(csub_opt, color):
pass
assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt']
assert list(get_choices(cli, 'lol', [], '')) == ['asub']
assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt']
assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub']
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt']
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub']
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt']
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == []
assert choices_without_help(cli, [], '-') == ['--cli-opt']
assert choices_without_help(cli, [], '') == ['asub']
assert choices_without_help(cli, ['asub'], '-') == ['--asub-opt']
assert choices_without_help(cli, ['asub'], '') == ['bsub']
assert choices_without_help(cli, ['asub', 'bsub'], '-') == ['--bsub-opt']
assert choices_without_help(cli, ['asub', 'bsub'], '') == ['csub']
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '-') == ['--csub-opt', '--csub', '--search-color']
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--csub-opt'], '') == CSUB_OPT_CHOICES
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '--csub') == ['--csub-opt', '--csub']
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--csub'], '') == CSUB_CHOICES
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--csub-opt'], 'f') == ['foo']
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '') == COLORS
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], 'b') == ['blue']
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--search-color'], 'een') == ['green']
def test_chaining():
@click.group('cli', chain=True)
@click.option('--cli-opt')
def cli(cli_opt):
pass
@cli.command()
@click.option('--asub-opt')
def asub(asub_opt):
pass
@cli.command(help='bsub help')
@click.option('--bsub-opt')
@click.argument('arg', type=click.Choice(['arg1', 'arg2']), required=True)
def bsub(bsub_opt, arg):
pass
@cli.command()
@click.option('--csub-opt')
@click.argument('arg', type=click.Choice(['carg1', 'carg2']), required=False)
def csub(csub_opt, arg):
pass
assert choices_without_help(cli, [], '-') == ['--cli-opt']
assert choices_without_help(cli, [], '') == ['asub', 'bsub', 'csub']
assert choices_without_help(cli, ['asub'], '-') == ['--asub-opt']
assert choices_without_help(cli, ['asub'], '') == ['bsub', 'csub']
assert choices_without_help(cli, ['bsub'], '') == ['arg1', 'arg2']
assert choices_without_help(cli, ['asub', '--asub-opt'], '') == []
assert choices_without_help(cli, ['asub', '--asub-opt', '5', 'bsub'], '-') == ['--bsub-opt']
assert choices_without_help(cli, ['asub', 'bsub'], '-') == ['--bsub-opt']
assert choices_with_help(cli, ['asub'], 'b') == [('bsub', 'bsub help')]
assert choices_without_help(cli, ['asub', 'csub'], '-') == ['--csub-opt']
def test_argument_choice():
@click.command()
@click.argument('arg1', required=False, type=click.Choice(['arg11', 'arg12']))
@click.argument('arg2', required=False, type=click.Choice(['arg21', 'arg22']))
@click.argument('arg3', required=False, type=click.Choice(['arg', 'argument']))
def cli():
pass
assert choices_without_help(cli, [], '') == ['arg11', 'arg12']
assert choices_without_help(cli, [], 'arg') == ['arg11', 'arg12']
assert choices_without_help(cli, ['arg11'], '') == ['arg21', 'arg22']
assert choices_without_help(cli, ['arg12', 'arg21'], '') == ['arg', 'argument']
assert choices_without_help(cli, ['arg12', 'arg21'], 'argu') == ['argument']
def test_option_choice():
@click.command()
@click.option('--opt1', type=click.Choice(['opt11', 'opt12']), help='opt1 help')
@click.option('--opt2', type=click.Choice(['opt21', 'opt22']))
@click.option('--opt3', type=click.Choice(['opt', 'option']))
def cli():
pass
assert choices_with_help(cli, [], '-') == [('--opt1', 'opt1 help'),
('--opt2', None),
('--opt3', None)]
assert choices_without_help(cli, [], '--opt') == ['--opt1', '--opt2', '--opt3']
assert choices_without_help(cli, [], '--opt1=') == ['opt11', 'opt12']
assert choices_without_help(cli, [], '--opt2=') == ['opt21', 'opt22']
assert choices_without_help(cli, ['--opt2'], '=') == ['opt21', 'opt22']
assert choices_without_help(cli, ['--opt2', '='], 'opt') == ['opt21', 'opt22']
assert choices_without_help(cli, ['--opt1'], '') == ['opt11', 'opt12']
assert choices_without_help(cli, ['--opt2'], '') == ['opt21', 'opt22']
assert choices_without_help(cli, ['--opt1', 'opt11', '--opt2'], '') == ['opt21', 'opt22']
assert choices_without_help(cli, ['--opt2', 'opt21'], '-') == ['--opt1', '--opt3']
assert choices_without_help(cli, ['--opt1', 'opt11'], '-') == ['--opt2', '--opt3']
assert choices_without_help(cli, ['--opt1'], 'opt') == ['opt11', 'opt12']
assert choices_without_help(cli, ['--opt3'], 'opti') == ['option']
assert choices_without_help(cli, ['--opt1', 'invalid_opt'], '-') == ['--opt2', '--opt3']
def test_option_and_arg_choice():
@click.command()
@click.option('--opt1', type=click.Choice(['opt11', 'opt12']))
@click.argument('arg1', required=False, type=click.Choice(['arg11', 'arg12']))
@click.option('--opt2', type=click.Choice(['opt21', 'opt22']))
def cli():
pass
assert choices_without_help(cli, ['--opt1'], '') == ['opt11', 'opt12']
assert choices_without_help(cli, [''], '--opt1=') == ['opt11', 'opt12']
assert choices_without_help(cli, [], '') == ['arg11', 'arg12']
assert choices_without_help(cli, ['--opt2'], '') == ['opt21', 'opt22']
def test_boolean_flag_choice():
@click.command()
@click.option('--shout/--no-shout', default=False)
@click.argument('arg', required=False, type=click.Choice(['arg1', 'arg2']))
def cli(local_opt):
pass
assert choices_without_help(cli, [], '-') == ['--shout', '--no-shout']
assert choices_without_help(cli, ['--shout'], '') == ['arg1', 'arg2']
def test_multi_value_option_choice():
@click.command()
@click.option('--pos', nargs=2, type=click.Choice(['pos1', 'pos2']))
@click.argument('arg', required=False, type=click.Choice(['arg1', 'arg2']))
def cli(local_opt):
pass
assert choices_without_help(cli, ['--pos'], '') == ['pos1', 'pos2']
assert choices_without_help(cli, ['--pos', 'pos1'], '') == ['pos1', 'pos2']
assert choices_without_help(cli, ['--pos', 'pos1', 'pos2'], '') == ['arg1', 'arg2']
assert choices_without_help(cli, ['--pos', 'pos1', 'pos2', 'arg1'], '') == []
def test_multi_option_choice():
@click.command()
@click.option('--message', '-m', multiple=True, type=click.Choice(['m1', 'm2']))
@click.argument('arg', required=False, type=click.Choice(['arg1', 'arg2']))
def cli(local_opt):
pass
assert choices_without_help(cli, ['-m'], '') == ['m1', 'm2']
assert choices_without_help(cli, ['-m', 'm1', '-m'], '') == ['m1', 'm2']
assert choices_without_help(cli, ['-m', 'm1'], '') == ['arg1', 'arg2']
def test_variadic_argument_choice():
@click.command()
@click.argument('src', nargs=-1, type=click.Choice(['src1', 'src2']))
def cli(local_opt):
pass
assert choices_without_help(cli, ['src1', 'src2'], '') == ['src1', 'src2']
def test_long_chain_choice():
@click.group()
def cli():
pass
@cli.group()
@click.option('--sub-opt', type=click.Choice(['subopt1', 'subopt2']))
@click.argument('sub-arg', required=False, type=click.Choice(['subarg1', 'subarg2']))
def sub(sub_opt):
pass
@sub.command(short_help='bsub help')
@click.option('--bsub-opt', type=click.Choice(['bsubopt1', 'bsubopt2']))
@click.argument('bsub-arg1', required=False, type=click.Choice(['bsubarg1', 'bsubarg2']))
@click.argument('bbsub-arg2', required=False, type=click.Choice(['bbsubarg1', 'bbsubarg2']))
def bsub(bsub_opt):
pass
assert choices_with_help(cli, ['sub'], '') == [('subarg1', None), ('subarg2', None), ('bsub', 'bsub help')]
assert choices_without_help(cli, ['sub', '--sub-opt'], '') == ['subopt1', 'subopt2']
assert choices_without_help(cli, ['sub', '--sub-opt', 'subopt1'], '') == \
['subarg1', 'subarg2', 'bsub']
assert choices_without_help(cli,
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '-') == ['--bsub-opt']
assert choices_without_help(cli,
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt'], '') == \
['bsubopt1', 'bsubopt2']
assert choices_without_help(cli,
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt', 'bsubopt1', 'bsubarg1'],
'') == ['bbsubarg1', 'bbsubarg2']

View file

@ -180,6 +180,28 @@ def test_boolean_option(runner):
assert result.output == '%s\n' % (default)
def test_boolean_conversion(runner):
for default in True, False:
@click.command()
@click.option('--flag', default=default, type=bool)
def cli(flag):
click.echo(flag)
for value in 'true', 't', '1', 'yes', 'y':
result = runner.invoke(cli, ['--flag', value])
assert not result.exception
assert result.output == 'True\n'
for value in 'false', 'f', '0', 'no', 'n':
result = runner.invoke(cli, ['--flag', value])
assert not result.exception
assert result.output == 'False\n'
result = runner.invoke(cli, [])
assert not result.exception
assert result.output == '%s\n' % default
def test_file_option(runner):
@click.command()
@click.option('--file', type=click.File('w'))
@ -343,6 +365,39 @@ def test_int_range_option(runner):
assert result.output == '0\n'
def test_float_range_option(runner):
@click.command()
@click.option('--x', type=click.FloatRange(0, 5))
def cli(x):
click.echo(x)
result = runner.invoke(cli, ['--x=5.0'])
assert not result.exception
assert result.output == '5.0\n'
result = runner.invoke(cli, ['--x=6.0'])
assert result.exit_code == 2
assert 'Invalid value for "--x": 6.0 is not in the valid range of 0 to 5.\n' \
in result.output
@click.command()
@click.option('--x', type=click.FloatRange(0, 5, clamp=True))
def clamp(x):
click.echo(x)
result = runner.invoke(clamp, ['--x=5.0'])
assert not result.exception
assert result.output == '5.0\n'
result = runner.invoke(clamp, ['--x=6.0'])
assert not result.exception
assert result.output == '5\n'
result = runner.invoke(clamp, ['--x=-1.0'])
assert not result.exception
assert result.output == '0\n'
def test_required_option(runner):
@click.command()
@click.option('--foo', required=True)
@ -357,7 +412,7 @@ def test_required_option(runner):
def test_evaluation_order(runner):
called = []
def memo(ctx, value):
def memo(ctx, param, value):
called.append(value)
return value
@ -397,3 +452,47 @@ def test_evaluation_order(runner):
'normal1',
'missing',
]
def test_hidden_option(runner):
@click.command()
@click.option('--nope', hidden=True)
def cli(nope):
click.echo(nope)
result = runner.invoke(cli, ['--help'])
assert result.exit_code == 0
assert '--nope' not in result.output
def test_hidden_command(runner):
@click.group()
def cli():
pass
@cli.command(hidden=True)
def nope():
pass
result = runner.invoke(cli, ['--help'])
assert result.exit_code == 0
assert 'nope' not in result.output
def test_hidden_group(runner):
@click.group()
def cli():
pass
@cli.group(hidden=True)
def subgroup():
pass
@subgroup.command()
def nope():
pass
result = runner.invoke(cli, ['--help'])
assert result.exit_code == 0
assert 'subgroup' not in result.output
assert 'nope' not in result.output

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import re
import click
import pytest
def test_other_command_invoke(runner):
@ -60,9 +62,9 @@ def test_auto_shorthelp(runner):
result = runner.invoke(cli, ['--help'])
assert re.search(
r'Commands:\n\s+'
r'long\s+This is a long text that is too long to show\.\.\.\n\s+'
r'long\s+This is a long text that is too long to show as short help\.\.\.\n\s+'
r'short\s+This is a short text\.\n\s+'
r'special_chars\s+Login and store the token in ~/.netrc\.\s*',
r'special-chars\s+Login and store the token in ~/.netrc\.\s*',
result.output) is not None
@ -206,7 +208,7 @@ def test_other_command_invoke_with_defaults(runner):
@click.option('--foo', type=click.INT, default=42)
@click.pass_context
def other_cmd(ctx, foo):
assert ctx.info_name == 'other_cmd'
assert ctx.info_name == 'other-cmd'
click.echo(foo)
result = runner.invoke(cli, [])
@ -253,3 +255,29 @@ def test_unprocessed_options(runner):
'Verbosity: 4',
'Args: -foo|-x|--muhaha|x|y|-x',
]
def test_deprecated_in_help_messages(runner):
@click.command(deprecated=True)
def cmd_with_help():
"""CLI HELP"""
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'])
assert '(DEPRECATED)' in result.output
def test_deprecated_in_invocation(runner):
@click.command(deprecated=True)
def deprecated_cmd():
debug()
result = runner.invoke(deprecated_cmd)
assert 'DeprecationWarning:' in result.output

View file

@ -1,4 +1,5 @@
import click
import pytest
if click.__version__ >= '3.0':
@ -11,14 +12,14 @@ if click.__version__ >= '3.0':
def cli(foo):
click.echo(foo)
result = runner.invoke(cli, ['--foo', 'wat'])
assert result.exit_code == 0
assert 'WAT' in result.output
assert 'Invoked legacy parameter callback' in result.output
with pytest.warns(Warning, match='Invoked legacy parameter callback'):
result = runner.invoke(cli, ['--foo', 'wat'])
assert result.exit_code == 0
assert 'WAT' in result.output
def test_bash_func_name():
from click._bashcomplete import get_completion_script
script = get_completion_script('foo-bar baz_blah', '_COMPLETE_VAR').strip()
script = get_completion_script('foo-bar baz_blah', '_COMPLETE_VAR', 'bash').strip()
assert script.startswith('_foo_barbaz_blah_completion()')
assert '_COMPLETE_VAR=complete $1' in script

View file

@ -193,6 +193,7 @@ def test_close_before_pop(runner):
@click.pass_context
def cli(ctx):
ctx.obj = 'test'
@ctx.call_on_close
def foo():
assert click.get_current_context().obj == 'test'
@ -203,3 +204,55 @@ def test_close_before_pop(runner):
assert not result.exception
assert result.output == 'aha!\n'
assert called == [True]
def test_make_pass_decorator_args(runner):
"""
Test to check that make_pass_decorator doesn't consume arguments based on
invocation order.
"""
class Foo(object):
title = 'foocmd'
pass_foo = click.make_pass_decorator(Foo)
@click.group()
@click.pass_context
def cli(ctx):
ctx.obj = Foo()
@cli.command()
@click.pass_context
@pass_foo
def test1(foo, ctx):
click.echo(foo.title)
@cli.command()
@pass_foo
@click.pass_context
def test2(ctx, foo):
click.echo(foo.title)
result = runner.invoke(cli, ['test1'])
assert not result.exception
assert result.output == 'foocmd\n'
result = runner.invoke(cli, ['test2'])
assert not result.exception
assert result.output == 'foocmd\n'
def test_exit_not_standalone():
@click.command()
@click.pass_context
def cli(ctx):
ctx.exit(1)
assert cli.main([], 'test_exit_not_standalone', standalone_mode=False) == 1
@click.command()
@click.pass_context
def cli(ctx):
ctx.exit(0)
assert cli.main([], 'test_exit_not_standalone', standalone_mode=False) == 0

View file

@ -74,11 +74,11 @@ def test_wrapping_long_options_strings(runner):
# 54 is chosen as a length where the second line is one character
# longer than the maximum length.
result = runner.invoke(cli, ['a_very_long', 'command', '--help'],
result = runner.invoke(cli, ['a-very-long', 'command', '--help'],
terminal_width=54)
assert not result.exception
assert result.output.splitlines() == [
'Usage: cli a_very_long command [OPTIONS] FIRST SECOND',
'Usage: cli a-very-long command [OPTIONS] FIRST SECOND',
' THIRD FOURTH FIFTH',
' SIXTH',
'',
@ -111,11 +111,11 @@ def test_wrapping_long_command_name(runner):
"""A command.
"""
result = runner.invoke(cli, ['a_very_very_very_long', 'command', '--help'],
result = runner.invoke(cli, ['a-very-very-very-long', 'command', '--help'],
terminal_width=54)
assert not result.exception
assert result.output.splitlines() == [
'Usage: cli a_very_very_very_long command ',
'Usage: cli a-very-very-very-long command ',
' [OPTIONS] FIRST SECOND THIRD FOURTH FIFTH',
' SIXTH',
'',
@ -145,3 +145,155 @@ def test_formatting_empty_help_lines(runner):
'Options:',
' --help Show this message and exit.',
]
def test_formatting_usage_error(runner):
@click.command()
@click.argument('arg')
def cmd(arg):
click.echo('arg:' + arg)
result = runner.invoke(cmd, [])
assert result.exit_code == 2
assert result.output.splitlines() == [
'Usage: cmd [OPTIONS] ARG',
'Try "cmd --help" for help.',
'',
'Error: Missing argument "ARG".'
]
def test_formatting_usage_error_metavar_missing_arg(runner):
"""
:author: @r-m-n
Including attribution to #612
"""
@click.command()
@click.argument('arg', metavar='metavar')
def cmd(arg):
pass
result = runner.invoke(cmd, [])
assert result.exit_code == 2
assert result.output.splitlines() == [
'Usage: cmd [OPTIONS] metavar',
'Try "cmd --help" for help.',
'',
'Error: Missing argument "metavar".'
]
def test_formatting_usage_error_metavar_bad_arg(runner):
@click.command()
@click.argument('arg', type=click.INT, metavar='metavar')
def cmd(arg):
pass
result = runner.invoke(cmd, ['3.14'])
assert result.exit_code == 2
assert result.output.splitlines() == [
'Usage: cmd [OPTIONS] metavar',
'Try "cmd --help" for help.',
'',
'Error: Invalid value for "metavar": 3.14 is not a valid integer'
]
def test_formatting_usage_error_nested(runner):
@click.group()
def cmd():
pass
@cmd.command()
@click.argument('bar')
def foo(bar):
click.echo('foo:' + bar)
result = runner.invoke(cmd, ['foo'])
assert result.exit_code == 2
assert result.output.splitlines() == [
'Usage: cmd foo [OPTIONS] BAR',
'Try "cmd foo --help" for help.',
'',
'Error: Missing argument "BAR".'
]
def test_formatting_usage_error_no_help(runner):
@click.command(add_help_option=False)
@click.argument('arg')
def cmd(arg):
click.echo('arg:' + arg)
result = runner.invoke(cmd, [])
assert result.exit_code == 2
assert result.output.splitlines() == [
'Usage: cmd [OPTIONS] ARG',
'',
'Error: Missing argument "ARG".'
]
def test_formatting_usage_custom_help(runner):
@click.command(context_settings=dict(help_option_names=['--man']))
@click.argument('arg')
def cmd(arg):
click.echo('arg:' + arg)
result = runner.invoke(cmd, [])
assert result.exit_code == 2
assert result.output.splitlines() == [
'Usage: cmd [OPTIONS] ARG',
'Try "cmd --man" for help.',
'',
'Error: Missing argument "ARG".'
]
def test_formatting_custom_type_metavar(runner):
class MyType(click.ParamType):
def get_metavar(self, param):
return "MY_TYPE"
@click.command("foo")
@click.help_option()
@click.argument("param", type=MyType())
def cmd(param):
pass
result = runner.invoke(cmd, '--help')
assert not result.exception
assert result.output.splitlines() == [
'Usage: foo [OPTIONS] MY_TYPE',
'',
'Options:',
' --help Show this message and exit.'
]
def test_truncating_docstring(runner):
@click.command()
@click.pass_context
def cli(ctx):
"""First paragraph.
This is a very long second
paragraph and not correctly
wrapped but it will be rewrapped.
\f
:param click.core.Context ctx: Click context.
"""
result = runner.invoke(cli, ['--help'], terminal_width=60)
assert not result.exception
assert result.output.splitlines() == [
'Usage: cli [OPTIONS]',
'',
' First paragraph.',
'',
' This is a very long second paragraph and not correctly',
' wrapped but it will be rewrapped.',
'',
'Options:',
' --help Show this message and exit.',
]

View file

@ -32,7 +32,7 @@ click.echo(json.dumps(rv))
ALLOWED_IMPORTS = set([
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
'threading', 'colorama', 'errno'
'threading', 'colorama', 'errno', 'fcntl'
])
if WIN:
@ -48,7 +48,6 @@ def test_light_imports():
if sys.version_info[0] != 2:
rv = rv.decode('utf-8')
imported = json.loads(rv)
print(imported)
for module in imported:
if module == 'click' or module.startswith('click.'):

View file

@ -84,7 +84,7 @@ def test_counting(runner):
assert result.output == 'verbosity=0\n'
result = runner.invoke(cli, ['--help'])
assert re.search('-v\s+Verbosity', result.output) is not None
assert re.search(r'-v\s+Verbosity', result.output) is not None
@pytest.mark.parametrize('unknown_flag', ['--foo', '-f'])
@ -174,6 +174,33 @@ def test_multiple_default_type(runner):
'two --arg2 4 four'.split())
assert not result.exception
def test_dynamic_default_help_unset(runner):
@click.command()
@click.option('--username', prompt=True,
default=lambda: os.environ.get('USER', ''),
show_default=True)
def cmd(username):
print("Hello,", username)
result = runner.invoke(cmd, ['--help'])
assert result.exit_code == 0
assert '--username' in result.output
assert 'lambda' not in result.output
assert '(dynamic)' in result.output
def test_dynamic_default_help_text(runner):
@click.command()
@click.option('--username', prompt=True,
default=lambda: os.environ.get('USER', ''),
show_default='current user')
def cmd(username):
print("Hello,", username)
result = runner.invoke(cmd, ['--help'])
assert result.exit_code == 0
assert '--username' in result.output
assert 'lambda' not in result.output
assert '(current user)' in result.output
def test_nargs_envvar(runner):
@click.command()
@ -198,8 +225,32 @@ def test_nargs_envvar(runner):
assert result.output == 'x|1\ny|2\n'
def test_show_envvar(runner):
@click.command()
@click.option('--arg1', envvar='ARG1',
show_envvar=True)
def cmd(arg):
pass
result = runner.invoke(cmd, ['--help'])
assert not result.exception
assert 'ARG1' in result.output
def test_show_envvar_auto_prefix(runner):
@click.command()
@click.option('--arg1', show_envvar=True)
def cmd(arg):
pass
result = runner.invoke(cmd, ['--help'],
auto_envvar_prefix='TEST')
assert not result.exception
assert 'TEST_ARG1' in result.output
def test_custom_validation(runner):
def validate_pos_int(ctx, value):
def validate_pos_int(ctx, param, value):
if value < 0:
raise click.BadParameter('Value needs to be positive')
return value
@ -255,8 +306,42 @@ def test_missing_choice(runner):
result = runner.invoke(cmd)
assert result.exit_code == 2
assert 'Error: Missing option "--foo". Choose from foo, bar.' \
in result.output
error, separator, choices = result.output.partition('Choose from')
assert 'Error: Missing option "--foo". ' in error
assert 'Choose from' in separator
assert 'foo' in choices
assert 'bar' in choices
def test_case_insensitive_choice(runner):
@click.command()
@click.option('--foo', type=click.Choice(
['Orange', 'Apple'], case_sensitive=False))
def cmd(foo):
click.echo(foo)
result = runner.invoke(cmd, ['--foo', 'apple'])
assert result.exit_code == 0
result = runner.invoke(cmd, ['--foo', 'oRANGe'])
assert result.exit_code == 0
result = runner.invoke(cmd, ['--foo', 'Apple'])
assert result.exit_code == 0
@click.command()
@click.option('--foo', type=click.Choice(['Orange', 'Apple']))
def cmd2(foo):
click.echo(foo)
result = runner.invoke(cmd2, ['--foo', 'apple'])
assert result.exit_code == 2
result = runner.invoke(cmd2, ['--foo', 'oRANGe'])
assert result.exit_code == 2
result = runner.invoke(cmd2, ['--foo', 'Apple'])
assert result.exit_code == 0
def test_multiline_help(runner):
@ -278,7 +363,6 @@ def test_multiline_help(runner):
assert ' i am' in out
assert ' multiline' in out
def test_argument_custom_class(runner):
class CustomArgument(click.Argument):
def get_default(self, ctx):
@ -311,6 +395,35 @@ def test_option_custom_class(runner):
assert 'you wont see me' not in result.output
def test_option_custom_class_reusable(runner):
"""Ensure we can reuse a custom class option. See Issue #926"""
class CustomOption(click.Option):
def get_help_record(self, ctx):
'''a dumb override of a help text for testing'''
return ('--help', 'I am a help text')
# Assign to a variable to re-use the decorator.
testoption = click.option('--testoption', cls=CustomOption, help='you wont see me')
@click.command()
@testoption
def cmd1(testoption):
click.echo(testoption)
@click.command()
@testoption
def cmd2(testoption):
click.echo(testoption)
# 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
def test_aliases_for_flags(runner):
@click.command()
@click.option('--warnings/--no-warnings', ' /-W', default=True)
@ -335,3 +448,29 @@ def test_aliases_for_flags(runner):
assert result.output == 'False\n'
result = runner.invoke(cli_alt, ['-w'])
assert result.output == 'True\n'
@pytest.mark.parametrize('option_args,expected', [
(['--aggressive', '--all', '-a'], 'aggressive'),
(['--first', '--second', '--third', '-a', '-b', '-c'], 'first'),
(['--apple', '--banana', '--cantaloupe', '-a', '-b', '-c'], 'apple'),
(['--cantaloupe', '--banana', '--apple', '-c', '-b', '-a'], 'cantaloupe'),
(['-a', '-b', '-c'], 'a'),
(['-c', '-b', '-a'], 'c'),
(['-a', '--apple', '-b', '--banana', '-c', '--cantaloupe'], 'apple'),
(['-c', '-a', '--cantaloupe', '-b', '--banana', '--apple'], 'cantaloupe'),
(['--from', '-f', '_from'], '_from'),
(['--return', '-r', '_ret'], '_ret'),
])
def test_option_names(runner, option_args, expected):
@click.command()
@click.option(*option_args, is_flag=True)
def cmd(**kwargs):
click.echo(str(kwargs[expected]))
assert cmd.params[0].name == expected
for form in option_args:
if form.startswith('-'):
result = runner.invoke(cmd, [form])
assert result.output == 'True\n'

View file

@ -1,14 +1,130 @@
import click
import time
class FakeClock(object):
def __init__(self):
self.now = time.time()
def advance_time(self, seconds=1):
self.now += seconds
def time(self):
return self.now
def test_progressbar_strip_regression(runner, monkeypatch):
fake_clock = FakeClock()
label = ' padded line'
@click.command()
def cli():
with click.progressbar(tuple(range(10)), label=label) as progress:
for thing in progress:
pass
fake_clock.advance_time()
monkeypatch.setattr(time, 'time', fake_clock.time)
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
assert label in runner.invoke(cli, []).output
def test_progressbar_length_hint(runner, monkeypatch):
class Hinted(object):
def __init__(self, n):
self.items = list(range(n))
def __length_hint__(self):
return len(self.items)
def __iter__(self):
return self
def __next__(self):
if self.items:
return self.items.pop()
else:
raise StopIteration
next = __next__
fake_clock = FakeClock()
@click.command()
def cli():
with click.progressbar(Hinted(10), label='test') as progress:
for thing in progress:
fake_clock.advance_time()
monkeypatch.setattr(time, 'time', fake_clock.time)
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
result = runner.invoke(cli, [])
assert result.exception is None
def test_progressbar_hidden(runner, monkeypatch):
fake_clock = FakeClock()
label = 'whatever'
@click.command()
def cli():
with click.progressbar(tuple(range(10)), label=label) as progress:
for thing in progress:
fake_clock.advance_time()
monkeypatch.setattr(time, 'time', fake_clock.time)
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: False)
assert runner.invoke(cli, []).output == ''
def test_choices_list_in_prompt(runner, monkeypatch):
@click.command()
@click.option('-g', type=click.Choice(['none', 'day', 'week', 'month']),
prompt=True)
def cli_with_choices(g):
pass
@click.command()
@click.option('-g', type=click.Choice(['none', 'day', 'week', 'month']),
prompt=True, show_choices=False)
def cli_without_choices(g):
pass
result = runner.invoke(cli_with_choices, [], input='none')
assert '(none, day, week, month)' in result.output
result = runner.invoke(cli_without_choices, [], input='none')
assert '(none, day, week, month)' not in result.output
def test_secho(runner):
with runner.isolation() as outstreams:
click.secho(None, nl=False)
bytes = outstreams[0].getvalue()
assert bytes == b''
def test_progressbar_yields_all_items(runner):
with click.progressbar(range(3)) as progress:
assert len(list(progress)) == 3
def test_progressbar_update(runner, monkeypatch):
fake_clock = FakeClock()
@click.command()
def cli():
with click.progressbar(range(4)) as progress:
for _ in progress:
fake_clock.advance_time()
print("")
monkeypatch.setattr(time, 'time', fake_clock.time)
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
output = runner.invoke(cli, []).output
lines = [line for line in output.split('\n') if '[' in line]
assert ' 25% 00:00:03' in lines[0]
assert ' 50% 00:00:02' in lines[1]
assert ' 75% 00:00:01' in lines[2]
assert '100% ' in lines[3]

View file

@ -153,16 +153,34 @@ def test_exit_code_and_output_from_sys_exit():
click.echo('hello world')
sys.exit('error')
@click.command()
@click.pass_context
def cli_string_ctx_exit(ctx):
click.echo('hello world')
ctx.exit('error')
@click.command()
def cli_int():
click.echo('hello world')
sys.exit(1)
@click.command()
@click.pass_context
def cli_int_ctx_exit(ctx):
click.echo('hello world')
ctx.exit(1)
@click.command()
def cli_float():
click.echo('hello world')
sys.exit(1.0)
@click.command()
@click.pass_context
def cli_float_ctx_exit(ctx):
click.echo('hello world')
ctx.exit(1.0)
@click.command()
def cli_no_error():
click.echo('hello world')
@ -173,14 +191,26 @@ def test_exit_code_and_output_from_sys_exit():
assert result.exit_code == 1
assert result.output == 'hello world\nerror\n'
result = runner.invoke(cli_string_ctx_exit)
assert result.exit_code == 1
assert result.output == 'hello world\nerror\n'
result = runner.invoke(cli_int)
assert result.exit_code == 1
assert result.output == 'hello world\n'
result = runner.invoke(cli_int_ctx_exit)
assert result.exit_code == 1
assert result.output == 'hello world\n'
result = runner.invoke(cli_float)
assert result.exit_code == 1
assert result.output == 'hello world\n1.0\n'
result = runner.invoke(cli_float_ctx_exit)
assert result.exit_code == 1
assert result.output == 'hello world\n1.0\n'
result = runner.invoke(cli_no_error)
assert result.exit_code == 0
assert result.output == 'hello world\n'
@ -202,3 +232,58 @@ def test_env():
assert result.output == 'ENV=some_value\n'
assert os.environ == env_orig
def test_stderr():
@click.command()
def cli_stderr():
click.echo("stdout")
click.echo("stderr", err=True)
runner = CliRunner(mix_stderr=False)
result = runner.invoke(cli_stderr)
assert result.output == 'stdout\n'
assert result.stdout == 'stdout\n'
assert result.stderr == 'stderr\n'
runner_mix = CliRunner(mix_stderr=True)
result_mix = runner_mix.invoke(cli_stderr)
assert result_mix.output == 'stdout\nstderr\n'
assert result_mix.stdout == 'stdout\nstderr\n'
with pytest.raises(ValueError):
result_mix.stderr
@pytest.mark.parametrize('args, expected_output', [
(None, 'bar\n'),
([], 'bar\n'),
('', 'bar\n'),
(['--foo', 'one two'], 'one two\n'),
('--foo "one two"', 'one two\n'),
])
def test_args(args, expected_output):
@click.command()
@click.option('--foo', default='bar')
def cli_args(foo):
click.echo(foo)
runner = CliRunner()
result = runner.invoke(cli_args, args=args)
assert result.exit_code == 0
assert result.output == expected_output
def test_setting_prog_name_in_extra():
@click.command()
def cli():
click.echo("ok")
runner = CliRunner()
result = runner.invoke(cli, prog_name="foobar")
assert not result.exception
assert result.output == 'ok\n'

View file

@ -10,13 +10,13 @@ from click._compat import WIN, PY2
def test_echo(runner):
with runner.isolation() as out:
with runner.isolation() as outstreams:
click.echo(u'\N{SNOWMAN}')
click.echo(b'\x44\x44')
click.echo(42, nl=False)
click.echo(b'a', nl=False)
click.echo('\x1b[31mx\x1b[39m', nl=False)
bytes = out.getvalue().replace(b'\r\n', b'\n')
bytes = outstreams[0].getvalue().replace(b'\r\n', b'\n')
assert bytes == b'\xe2\x98\x83\nDD\n42ax'
# If we are in Python 2, we expect that writing bytes into a string io
@ -35,12 +35,12 @@ def test_echo(runner):
def cli():
click.echo(b'\xf6')
result = runner.invoke(cli, [])
assert result.output_bytes == b'\xf6\n'
assert result.stdout_bytes == b'\xf6\n'
# Ensure we do not strip for bytes.
with runner.isolation() as out:
with runner.isolation() as outstreams:
click.echo(bytearray(b'\x1b[31mx\x1b[39m'), nl=False)
assert out.getvalue() == b'\x1b[31mx\x1b[39m'
assert outstreams[0].getvalue() == b'\x1b[31mx\x1b[39m'
def test_echo_custom_file():
@ -146,14 +146,36 @@ def test_prompts_abort(monkeypatch, capsys):
assert out == 'Password: \nScrew you.\n'
def _test_gen_func():
yield 'a'
yield 'b'
yield 'c'
yield 'abc'
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
def test_echo_via_pager(monkeypatch, capfd, cat):
@pytest.mark.parametrize('test', [
# We need lambda here, because pytest will
# reuse the parameters, and then the generators
# are already used and will not yield anymore
('just text\n', lambda: 'just text'),
('iterable\n', lambda: ["itera", "ble"]),
('abcabc\n', lambda: _test_gen_func),
('abcabc\n', lambda: _test_gen_func()),
('012345\n', lambda: (c for c in range(6))),
])
def test_echo_via_pager(monkeypatch, capfd, cat, test):
monkeypatch.setitem(os.environ, 'PAGER', cat)
monkeypatch.setattr(click._termui_impl, 'isatty', lambda x: True)
click.echo_via_pager('haha')
expected_output = test[0]
test_input = test[1]()
click.echo_via_pager(test_input)
out, err = capfd.readouterr()
assert out == 'haha\n'
assert out == expected_output
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
@ -268,9 +290,9 @@ def test_iter_keepopenfile(tmpdir):
expected = list(map(str, range(10)))
p = tmpdir.mkdir('testdir').join('testfile')
p.write(os.linesep.join(expected))
f = p.open()
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
assert e_line == a_line.strip()
with p.open() as f:
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
assert e_line == a_line.strip()
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
@ -278,6 +300,7 @@ def test_iter_lazyfile(tmpdir):
expected = list(map(str, range(10)))
p = tmpdir.mkdir('testdir').join('testfile')
p.write(os.linesep.join(expected))
f = p.open()
for e_line, a_line in zip(expected, click.utils.LazyFile(f.name)):
assert e_line == a_line.strip()
with p.open() as f:
with click.utils.LazyFile(f.name) as lf:
for e_line, a_line in zip(expected, lf):
assert e_line == a_line.strip()

35
tox.ini Normal file
View file

@ -0,0 +1,35 @@
[tox]
envlist = py{36,35,34,27,py}
[testenv]
passenv = LANG
deps =
pytest
coverage
colorama
commands = coverage run -p -m pytest {posargs:tests}
[testenv:docs-html]
deps = sphinx
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
[testenv:docs-linkcheck]
deps = sphinx
commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs {envtmpdir}/linkcheck
[testenv:coverage-report]
deps = coverage
skip_install = true
commands =
coverage combine
coverage report
coverage html
[testenv:codecov]
passenv = CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_*
deps = codecov
skip_install = true
commands =
coverage combine
coverage report
codecov