Update upstream source from tag 'upstream/6.7+git20180829'
Update to upstream version '6.7+git20180829'
with Debian dir d084b72790
28
.appveyor.yml
Normal 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
|
@ -0,0 +1,3 @@
|
||||||
|
[run]
|
||||||
|
branch = true
|
||||||
|
source = click,tests
|
15
.gitignore
vendored
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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.
|
17
MANIFEST.in
|
@ -1,12 +1,9 @@
|
||||||
include Makefile CHANGES LICENSE
|
include Makefile CHANGES LICENSE
|
||||||
recursive-include artwork *
|
|
||||||
recursive-include tests *
|
graft artwork
|
||||||
recursive-include examples *
|
graft tests
|
||||||
recursive-include docs *
|
graft examples
|
||||||
recursive-exclude docs *.pyc
|
graft docs
|
||||||
recursive-exclude docs *.pyo
|
|
||||||
recursive-exclude tests *.pyc
|
|
||||||
recursive-exclude tests *.pyo
|
|
||||||
recursive-exclude examples *.pyc
|
|
||||||
recursive-exclude examples *.pyo
|
|
||||||
prune docs/_build
|
prune docs/_build
|
||||||
|
|
||||||
|
global-exclude *.py[co] .DS_Store
|
||||||
|
|
2
Makefile
|
@ -1,5 +1,5 @@
|
||||||
test:
|
test:
|
||||||
@cd tests; PYTHONPATH=.. py.test --tb=short
|
@cd tests; PYTHONPATH=.. pytest --tb=short
|
||||||
|
|
||||||
upload-docs:
|
upload-docs:
|
||||||
$(MAKE) -C docs dirhtml
|
$(MAKE) -C docs dirhtml
|
||||||
|
|
13
PKG-INFO
|
@ -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
|
@ -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
|
@ -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
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
click
|
|
|
@ -28,7 +28,7 @@ from .decorators import pass_context, pass_obj, make_pass_decorator, \
|
||||||
|
|
||||||
# Types
|
# Types
|
||||||
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
||||||
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED
|
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
||||||
|
@ -66,7 +66,7 @@ __all__ = [
|
||||||
|
|
||||||
# Types
|
# Types
|
||||||
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
|
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
|
||||||
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
|
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', 'FloatRange',
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||||
|
@ -95,4 +95,4 @@ __all__ = [
|
||||||
disable_unicode_literals_warning = False
|
disable_unicode_literals_warning = False
|
||||||
|
|
||||||
|
|
||||||
__version__ = '6.7'
|
__version__ = '7.0-dev'
|
||||||
|
|
|
@ -1,27 +1,63 @@
|
||||||
|
import collections
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
from .parser import split_arg_string
|
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() {
|
%(complete_func)s() {
|
||||||
|
local IFS=$'\n'
|
||||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||||
COMP_CWORD=$COMP_CWORD \\
|
COMP_CWORD=$COMP_CWORD \\
|
||||||
%(autocomplete_var)s=complete $1 ) )
|
%(autocomplete_var)s=complete $1 ) )
|
||||||
return 0
|
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_]')
|
_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('-', '_'))
|
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,
|
'complete_func': '_%s_completion' % cf_name,
|
||||||
'script_names': prog_name,
|
'script_names': prog_name,
|
||||||
'autocomplete_var': complete_var,
|
'autocomplete_var': complete_var,
|
||||||
|
@ -29,37 +65,169 @@ def get_completion_script(prog_name, complete_var):
|
||||||
|
|
||||||
|
|
||||||
def resolve_ctx(cli, prog_name, args):
|
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)
|
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
||||||
while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
|
args_remaining = ctx.protected_args + ctx.args
|
||||||
a = ctx.protected_args + ctx.args
|
while ctx is not None and args_remaining:
|
||||||
cmd = ctx.command.get_command(ctx, a[0])
|
if isinstance(ctx.command, MultiCommand):
|
||||||
if cmd is None:
|
cmd = ctx.command.get_command(ctx, args_remaining[0])
|
||||||
return None
|
if cmd is None:
|
||||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
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
|
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):
|
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)
|
ctx = resolve_ctx(cli, prog_name, args)
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
return
|
return []
|
||||||
|
|
||||||
choices = []
|
# In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
|
||||||
if incomplete and not incomplete[:1].isalnum():
|
# 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:
|
for param in ctx.command.params:
|
||||||
if not isinstance(param, Option):
|
if isinstance(param, Option):
|
||||||
continue
|
param_opts = [param_opt for param_opt in param.opts +
|
||||||
choices.extend(param.opts)
|
param.secondary_opts if param_opt not in all_args or param.multiple]
|
||||||
choices.extend(param.secondary_opts)
|
completions.extend(
|
||||||
elif isinstance(ctx.command, MultiCommand):
|
[(o, param.help) for o in param_opts if o.startswith(incomplete)])
|
||||||
choices.extend(ctx.command.list_commands(ctx))
|
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:
|
add_subcommand_completions(ctx, incomplete, completions)
|
||||||
if item.startswith(incomplete):
|
return completions
|
||||||
yield item
|
|
||||||
|
|
||||||
|
|
||||||
def do_complete(cli, prog_name):
|
def do_complete(cli, prog_name, include_descriptions):
|
||||||
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
||||||
cword = int(os.environ['COMP_CWORD'])
|
cword = int(os.environ['COMP_CWORD'])
|
||||||
args = cwords[1:cword]
|
args = cwords[1:cword]
|
||||||
|
@ -69,15 +237,19 @@ def do_complete(cli, prog_name):
|
||||||
incomplete = ''
|
incomplete = ''
|
||||||
|
|
||||||
for item in get_choices(cli, prog_name, args, 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
|
return True
|
||||||
|
|
||||||
|
|
||||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||||
if complete_instr == 'source':
|
if complete_instr.startswith('source'):
|
||||||
echo(get_completion_script(prog_name, complete_var))
|
shell = 'zsh' if complete_instr == 'source_zsh' else 'bash'
|
||||||
|
echo(get_completion_script(prog_name, complete_var, shell))
|
||||||
return True
|
return True
|
||||||
elif complete_instr == 'complete':
|
elif complete_instr == 'complete' or complete_instr == 'complete_zsh':
|
||||||
return do_complete(cli, prog_name)
|
return do_complete(cli, prog_name, complete_instr == 'complete_zsh')
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -7,24 +7,31 @@ from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
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
|
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():
|
def get_filesystem_encoding():
|
||||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
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:
|
if encoding is None:
|
||||||
encoding = get_best_encoding(stream)
|
encoding = get_best_encoding(stream)
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
errors = 'replace'
|
||||||
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
||||||
line_buffering=True)
|
line_buffering=True,
|
||||||
|
force_readable=force_readable,
|
||||||
|
force_writable=force_writable)
|
||||||
|
|
||||||
|
|
||||||
def is_ascii_encoding(encoding):
|
def is_ascii_encoding(encoding):
|
||||||
|
@ -45,8 +52,10 @@ def get_best_encoding(stream):
|
||||||
|
|
||||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||||
|
|
||||||
def __init__(self, stream, encoding, errors, **extra):
|
def __init__(self, stream, encoding, errors,
|
||||||
self._stream = stream = _FixupStream(stream)
|
force_readable=False, force_writable=False, **extra):
|
||||||
|
self._stream = stream = _FixupStream(stream, force_readable,
|
||||||
|
force_writable)
|
||||||
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
||||||
|
|
||||||
# The io module is a place where the Python 3 text behavior
|
# 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
|
"""The new io interface needs more from streams than streams
|
||||||
traditionally implement. As such, this fix-up code is necessary in
|
traditionally implement. As such, this fix-up code is necessary in
|
||||||
some circumstances.
|
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._stream = stream
|
||||||
|
self._force_readable = force_readable
|
||||||
|
self._force_writable = force_writable
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self._stream, name)
|
return getattr(self._stream, name)
|
||||||
|
@ -101,6 +116,8 @@ class _FixupStream(object):
|
||||||
return self._stream.read(size)
|
return self._stream.read(size)
|
||||||
|
|
||||||
def readable(self):
|
def readable(self):
|
||||||
|
if self._force_readable:
|
||||||
|
return True
|
||||||
x = getattr(self._stream, 'readable', None)
|
x = getattr(self._stream, 'readable', None)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
return x()
|
return x()
|
||||||
|
@ -111,6 +128,8 @@ class _FixupStream(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def writable(self):
|
def writable(self):
|
||||||
|
if self._force_writable:
|
||||||
|
return True
|
||||||
x = getattr(self._stream, 'writable', None)
|
x = getattr(self._stream, 'writable', None)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
return x()
|
return x()
|
||||||
|
@ -139,6 +158,7 @@ if PY2:
|
||||||
bytes = str
|
bytes = str
|
||||||
raw_input = raw_input
|
raw_input = raw_input
|
||||||
string_types = (str, unicode)
|
string_types = (str, unicode)
|
||||||
|
int_types = (int, long)
|
||||||
iteritems = lambda x: x.iteritems()
|
iteritems = lambda x: x.iteritems()
|
||||||
range_type = xrange
|
range_type = xrange
|
||||||
|
|
||||||
|
@ -165,10 +185,13 @@ if PY2:
|
||||||
# available (which is why we use try-catch instead of the WIN variable
|
# 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
|
# here), such as the Google App Engine development server on Windows. In
|
||||||
# those cases there is just nothing we can do.
|
# those cases there is just nothing we can do.
|
||||||
|
def set_binary_mode(f):
|
||||||
|
return f
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
except ImportError:
|
except ImportError:
|
||||||
set_binary_mode = lambda x: x
|
pass
|
||||||
else:
|
else:
|
||||||
def set_binary_mode(f):
|
def set_binary_mode(f):
|
||||||
try:
|
try:
|
||||||
|
@ -179,6 +202,21 @@ if PY2:
|
||||||
msvcrt.setmode(fileno, os.O_BINARY)
|
msvcrt.setmode(fileno, os.O_BINARY)
|
||||||
return f
|
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):
|
def isidentifier(x):
|
||||||
return _identifier_re.search(x) is not None
|
return _identifier_re.search(x) is not None
|
||||||
|
|
||||||
|
@ -195,19 +233,22 @@ if PY2:
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
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):
|
def get_text_stdout(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
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):
|
def get_text_stderr(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
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):
|
def filename_to_ui(value):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
|
@ -218,6 +259,7 @@ else:
|
||||||
text_type = str
|
text_type = str
|
||||||
raw_input = input
|
raw_input = input
|
||||||
string_types = (str,)
|
string_types = (str,)
|
||||||
|
int_types = (int,)
|
||||||
range_type = range
|
range_type = range
|
||||||
isidentifier = lambda x: x.isidentifier()
|
isidentifier = lambda x: x.isidentifier()
|
||||||
iteritems = lambda x: iter(x.items())
|
iteritems = lambda x: iter(x.items())
|
||||||
|
@ -298,7 +340,8 @@ else:
|
||||||
|
|
||||||
return False
|
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):
|
if _is_binary_reader(text_reader, False):
|
||||||
binary_reader = text_reader
|
binary_reader = text_reader
|
||||||
else:
|
else:
|
||||||
|
@ -324,9 +367,11 @@ else:
|
||||||
# we're so fundamentally fucked that nothing can repair it.
|
# we're so fundamentally fucked that nothing can repair it.
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
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):
|
if _is_binary_writer(text_writer, False):
|
||||||
binary_writer = text_writer
|
binary_writer = text_writer
|
||||||
else:
|
else:
|
||||||
|
@ -352,7 +397,8 @@ else:
|
||||||
# we're so fundamentally fucked that nothing can repair it.
|
# we're so fundamentally fucked that nothing can repair it.
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
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():
|
def get_binary_stdin():
|
||||||
reader = _find_binary_reader(sys.stdin)
|
reader = _find_binary_reader(sys.stdin)
|
||||||
|
@ -379,19 +425,22 @@ else:
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
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):
|
def get_text_stdout(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
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):
|
def get_text_stderr(encoding=None, errors=None):
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
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):
|
def filename_to_ui(value):
|
||||||
if isinstance(value, bytes):
|
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
|
# Standard streams first. These are simple because they don't need
|
||||||
# special handling for the atomic flag. It's entirely ignored.
|
# special handling for the atomic flag. It's entirely ignored.
|
||||||
if filename == '-':
|
if filename == '-':
|
||||||
if 'w' in mode:
|
if any(m in mode for m in ['w', 'a', 'x']):
|
||||||
if 'b' in mode:
|
if 'b' in mode:
|
||||||
return get_binary_stdout(), False
|
return get_binary_stdout(), False
|
||||||
return get_text_stdout(encoding=encoding, errors=errors), 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:
|
else:
|
||||||
f = os.fdopen(fd, mode)
|
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.
|
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||||
|
|
|
@ -13,8 +13,10 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
|
import contextlib
|
||||||
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
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 .utils import echo
|
||||||
from .exceptions import ClickException
|
from .exceptions import ClickException
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ def _length_hint(obj):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return None
|
return None
|
||||||
if hint is NotImplemented or \
|
if hint is NotImplemented or \
|
||||||
not isinstance(hint, (int, long)) or \
|
not isinstance(hint, int_types) or \
|
||||||
hint < 0:
|
hint < 0:
|
||||||
return None
|
return None
|
||||||
return hint
|
return hint
|
||||||
|
@ -88,6 +90,7 @@ class ProgressBar(object):
|
||||||
self.current_item = None
|
self.current_item = None
|
||||||
self.is_hidden = not isatty(self.file)
|
self.is_hidden = not isatty(self.file)
|
||||||
self._last_line = None
|
self._last_line = None
|
||||||
|
self.short_limit = 0.5
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.entered = True
|
self.entered = True
|
||||||
|
@ -101,10 +104,13 @@ class ProgressBar(object):
|
||||||
if not self.entered:
|
if not self.entered:
|
||||||
raise RuntimeError('You need to use progress bars in a with block.')
|
raise RuntimeError('You need to use progress bars in a with block.')
|
||||||
self.render_progress()
|
self.render_progress()
|
||||||
return self
|
return self.generator()
|
||||||
|
|
||||||
|
def is_fast(self):
|
||||||
|
return time.time() - self.start <= self.short_limit
|
||||||
|
|
||||||
def render_finish(self):
|
def render_finish(self):
|
||||||
if self.is_hidden:
|
if self.is_hidden or self.is_fast():
|
||||||
return
|
return
|
||||||
self.file.write(AFTER_BAR)
|
self.file.write(AFTER_BAR)
|
||||||
self.file.flush()
|
self.file.flush()
|
||||||
|
@ -129,13 +135,13 @@ class ProgressBar(object):
|
||||||
|
|
||||||
def format_eta(self):
|
def format_eta(self):
|
||||||
if self.eta_known:
|
if self.eta_known:
|
||||||
t = self.eta + 1
|
t = int(self.eta)
|
||||||
seconds = t % 60
|
seconds = t % 60
|
||||||
t /= 60
|
t //= 60
|
||||||
minutes = t % 60
|
minutes = t % 60
|
||||||
t /= 60
|
t //= 60
|
||||||
hours = t % 24
|
hours = t % 24
|
||||||
t /= 24
|
t //= 24
|
||||||
if t > 0:
|
if t > 0:
|
||||||
days = t
|
days = t
|
||||||
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
||||||
|
@ -152,25 +158,27 @@ class ProgressBar(object):
|
||||||
def format_pct(self):
|
def format_pct(self):
|
||||||
return ('% 4d%%' % int(self.pct * 100))[1:]
|
return ('% 4d%%' % int(self.pct * 100))[1:]
|
||||||
|
|
||||||
def format_progress_line(self):
|
def format_bar(self):
|
||||||
show_percent = self.show_percent
|
|
||||||
|
|
||||||
info_bits = []
|
|
||||||
if self.length_known:
|
if self.length_known:
|
||||||
bar_length = int(self.pct * self.width)
|
bar_length = int(self.pct * self.width)
|
||||||
bar = self.fill_char * bar_length
|
bar = self.fill_char * bar_length
|
||||||
bar += self.empty_char * (self.width - bar_length)
|
bar += self.empty_char * (self.width - bar_length)
|
||||||
if show_percent is None:
|
elif self.finished:
|
||||||
show_percent = not self.show_pos
|
bar = self.fill_char * self.width
|
||||||
else:
|
else:
|
||||||
if self.finished:
|
bar = list(self.empty_char * (self.width or 1))
|
||||||
bar = self.fill_char * self.width
|
if self.time_per_iteration != 0:
|
||||||
else:
|
bar[int((math.cos(self.pos * self.time_per_iteration)
|
||||||
bar = list(self.empty_char * (self.width or 1))
|
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
||||||
if self.time_per_iteration != 0:
|
bar = ''.join(bar)
|
||||||
bar[int((math.cos(self.pos * self.time_per_iteration)
|
return bar
|
||||||
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
|
||||||
bar = ''.join(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:
|
if self.show_pos:
|
||||||
info_bits.append(self.format_pos())
|
info_bits.append(self.format_pos())
|
||||||
|
@ -185,49 +193,47 @@ class ProgressBar(object):
|
||||||
|
|
||||||
return (self.bar_template % {
|
return (self.bar_template % {
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
'bar': bar,
|
'bar': self.format_bar(),
|
||||||
'info': self.info_sep.join(info_bits)
|
'info': self.info_sep.join(info_bits)
|
||||||
}).rstrip()
|
}).rstrip()
|
||||||
|
|
||||||
def render_progress(self):
|
def render_progress(self):
|
||||||
from .termui import get_terminal_size
|
from .termui import get_terminal_size
|
||||||
nl = False
|
|
||||||
|
|
||||||
if self.is_hidden:
|
if self.is_hidden:
|
||||||
buf = [self.label]
|
return
|
||||||
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
|
|
||||||
|
|
||||||
clear_width = self.width
|
buf = []
|
||||||
if self.max_width is not None:
|
# Update width in case the terminal has been resized
|
||||||
clear_width = self.max_width
|
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)
|
clear_width = self.width
|
||||||
line = self.format_progress_line()
|
if self.max_width is not None:
|
||||||
line_len = term_len(line)
|
clear_width = self.max_width
|
||||||
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))
|
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)
|
line = ''.join(buf)
|
||||||
|
|
||||||
# Render the line only if it changed.
|
# 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
|
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()
|
self.file.flush()
|
||||||
|
|
||||||
def make_step(self, n_steps):
|
def make_step(self, n_steps):
|
||||||
|
@ -239,7 +245,16 @@ class ProgressBar(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.last_eta = time.time()
|
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
|
self.eta_known = self.length_known
|
||||||
|
|
||||||
|
@ -252,54 +267,56 @@ class ProgressBar(object):
|
||||||
self.current_item = None
|
self.current_item = None
|
||||||
self.finished = True
|
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:
|
if self.is_hidden:
|
||||||
return next(self.iter)
|
for rv in self.iter:
|
||||||
try:
|
yield rv
|
||||||
rv = next(self.iter)
|
else:
|
||||||
self.current_item = rv
|
for rv in self.iter:
|
||||||
except StopIteration:
|
self.current_item = rv
|
||||||
|
yield rv
|
||||||
|
self.update(1)
|
||||||
self.finish()
|
self.finish()
|
||||||
self.render_progress()
|
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."""
|
"""Decide what method to use for paging through text."""
|
||||||
stdout = _default_text_stdout()
|
stdout = _default_text_stdout()
|
||||||
if not isatty(sys.stdin) or not isatty(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()
|
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
||||||
if pager_cmd:
|
if pager_cmd:
|
||||||
if WIN:
|
if WIN:
|
||||||
return _tempfilepager(text, pager_cmd, color)
|
return _tempfilepager(generator, pager_cmd, color)
|
||||||
return _pipepager(text, pager_cmd, color)
|
return _pipepager(generator, pager_cmd, color)
|
||||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
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'):
|
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:
|
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
||||||
return _pipepager(text, 'less', color)
|
return _pipepager(generator, 'less', color)
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
fd, filename = tempfile.mkstemp()
|
fd, filename = tempfile.mkstemp()
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
try:
|
try:
|
||||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
||||||
return _pipepager(text, 'more', color)
|
return _pipepager(generator, 'more', color)
|
||||||
return _nullpager(stdout, text, color)
|
return _nullpager(stdout, generator, color)
|
||||||
finally:
|
finally:
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def _pipepager(text, cmd, color):
|
def _pipepager(generator, cmd, color):
|
||||||
"""Page through text by feeding it to another program. Invoking a
|
"""Page through text by feeding it to another program. Invoking a
|
||||||
pager through this might support colors.
|
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:
|
elif 'r' in less_flags or 'R' in less_flags:
|
||||||
color = True
|
color = True
|
||||||
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
|
|
||||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||||
env=env)
|
env=env)
|
||||||
encoding = get_best_encoding(c.stdin)
|
encoding = get_best_encoding(c.stdin)
|
||||||
try:
|
try:
|
||||||
c.stdin.write(text.encode(encoding, 'replace'))
|
for text in generator:
|
||||||
c.stdin.close()
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
|
||||||
|
c.stdin.write(text.encode(encoding, 'replace'))
|
||||||
except (IOError, KeyboardInterrupt):
|
except (IOError, KeyboardInterrupt):
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
c.stdin.close()
|
||||||
|
|
||||||
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
||||||
# search or other commands inside less).
|
# search or other commands inside less).
|
||||||
|
@ -346,10 +365,12 @@ def _pipepager(text, cmd, color):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def _tempfilepager(text, cmd, color):
|
def _tempfilepager(generator, cmd, color):
|
||||||
"""Page through text by invoking a program on a temporary file."""
|
"""Page through text by invoking a program on a temporary file."""
|
||||||
import tempfile
|
import tempfile
|
||||||
filename = tempfile.mktemp()
|
filename = tempfile.mktemp()
|
||||||
|
# TODO: This never terminates if the passed generator never terminates.
|
||||||
|
text = "".join(generator)
|
||||||
if not color:
|
if not color:
|
||||||
text = strip_ansi(text)
|
text = strip_ansi(text)
|
||||||
encoding = get_best_encoding(sys.stdout)
|
encoding = get_best_encoding(sys.stdout)
|
||||||
|
@ -361,11 +382,12 @@ def _tempfilepager(text, cmd, color):
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def _nullpager(stream, text, color):
|
def _nullpager(stream, generator, color):
|
||||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||||
if not color:
|
for text in generator:
|
||||||
text = strip_ansi(text)
|
if not color:
|
||||||
stream.write(text)
|
text = strip_ansi(text)
|
||||||
|
stream.write(text)
|
||||||
|
|
||||||
|
|
||||||
class Editor(object):
|
class Editor(object):
|
||||||
|
@ -478,6 +500,14 @@ def open_url(url, wait=False, locate=False):
|
||||||
args = 'start %s "" "%s"' % (
|
args = 'start %s "" "%s"' % (
|
||||||
wait and '/WAIT' or '', url.replace('"', ''))
|
wait and '/WAIT' or '', url.replace('"', ''))
|
||||||
return os.system(args)
|
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:
|
try:
|
||||||
if locate:
|
if locate:
|
||||||
|
@ -506,6 +536,10 @@ def _translate_ch_to_exc(ch):
|
||||||
if WIN:
|
if WIN:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal():
|
||||||
|
yield
|
||||||
|
|
||||||
def getchar(echo):
|
def getchar(echo):
|
||||||
rv = msvcrt.getch()
|
rv = msvcrt.getch()
|
||||||
if echo:
|
if echo:
|
||||||
|
@ -522,7 +556,8 @@ else:
|
||||||
import tty
|
import tty
|
||||||
import termios
|
import termios
|
||||||
|
|
||||||
def getchar(echo):
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal():
|
||||||
if not isatty(sys.stdin):
|
if not isatty(sys.stdin):
|
||||||
f = open('/dev/tty')
|
f = open('/dev/tty')
|
||||||
fd = f.fileno()
|
fd = f.fileno()
|
||||||
|
@ -533,9 +568,7 @@ else:
|
||||||
old_settings = termios.tcgetattr(fd)
|
old_settings = termios.tcgetattr(fd)
|
||||||
try:
|
try:
|
||||||
tty.setraw(fd)
|
tty.setraw(fd)
|
||||||
ch = os.read(fd, 32)
|
yield fd
|
||||||
if echo and isatty(sys.stdout):
|
|
||||||
sys.stdout.write(ch)
|
|
||||||
finally:
|
finally:
|
||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
@ -543,5 +576,11 @@ else:
|
||||||
f.close()
|
f.close()
|
||||||
except termios.error:
|
except termios.error:
|
||||||
pass
|
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')
|
||||||
|
|
|
@ -14,6 +14,8 @@ click = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||||
|
|
||||||
def _find_unicode_literals_frame():
|
def _find_unicode_literals_frame():
|
||||||
import __future__
|
import __future__
|
||||||
|
if not hasattr(sys, '_getframe'): # not all Python implementations have it
|
||||||
|
return 0
|
||||||
frm = sys._getframe(1)
|
frm = sys._getframe(1)
|
||||||
idx = 1
|
idx = 1
|
||||||
while frm is not None:
|
while frm is not None:
|
||||||
|
@ -60,8 +62,11 @@ def _verify_python3_env():
|
||||||
extra = ''
|
extra = ''
|
||||||
if os.name == 'posix':
|
if os.name == 'posix':
|
||||||
import subprocess
|
import subprocess
|
||||||
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
try:
|
||||||
stderr=subprocess.PIPE).communicate()[0]
|
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE).communicate()[0]
|
||||||
|
except OSError:
|
||||||
|
rv = b''
|
||||||
good_locales = set()
|
good_locales = set()
|
||||||
has_c_utf8 = False
|
has_c_utf8 = False
|
||||||
|
|
||||||
|
@ -94,7 +99,7 @@ def _verify_python3_env():
|
||||||
else:
|
else:
|
||||||
extra += (
|
extra += (
|
||||||
'This system lists a couple of UTF-8 supporting locales that\n'
|
'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'
|
'discovered: %s'
|
||||||
) % ', '.join(sorted(good_locales))
|
) % ', '.join(sorted(good_locales))
|
||||||
|
|
||||||
|
@ -114,5 +119,5 @@ def _verify_python3_env():
|
||||||
|
|
||||||
raise RuntimeError('Click will abort further execution because Python 3 '
|
raise RuntimeError('Click will abort further execution because Python 3 '
|
||||||
'was configured to use ASCII as encoding for the '
|
'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)
|
'for mitigation steps.' + extra)
|
||||||
|
|
|
@ -15,7 +15,7 @@ import zlib
|
||||||
import time
|
import time
|
||||||
import ctypes
|
import ctypes
|
||||||
import msvcrt
|
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, \
|
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
|
||||||
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
||||||
try:
|
try:
|
||||||
|
@ -261,7 +261,7 @@ def _get_windows_console_stream(f, encoding, errors):
|
||||||
func = _stream_factories.get(f.fileno())
|
func = _stream_factories.get(f.fileno())
|
||||||
if func is not None:
|
if func is not None:
|
||||||
if not PY2:
|
if not PY2:
|
||||||
f = getattr(f, 'buffer')
|
f = getattr(f, 'buffer', None)
|
||||||
if f is None:
|
if f is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
|
192
click/core.py
|
@ -1,4 +1,5 @@
|
||||||
import errno
|
import errno
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
@ -8,13 +9,13 @@ from functools import update_wrapper
|
||||||
from .types import convert_type, IntRange, BOOL
|
from .types import convert_type, IntRange, BOOL
|
||||||
from .utils import make_str, make_default_short_help, echo, get_os_args
|
from .utils import make_str, make_default_short_help, echo, get_os_args
|
||||||
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
||||||
MissingParameter
|
MissingParameter, Exit
|
||||||
from .termui import prompt, confirm
|
from .termui import prompt, confirm, style
|
||||||
from .formatting import HelpFormatter, join_options
|
from .formatting import HelpFormatter, join_options
|
||||||
from .parser import OptionParser, split_opt
|
from .parser import OptionParser, split_opt
|
||||||
from .globals import push_context, pop_context
|
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
|
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +25,24 @@ _missing = object()
|
||||||
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
|
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
|
||||||
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [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):
|
def _bashcomplete(cmd, prog_name, complete_var=None):
|
||||||
"""Internal handler for the bash completion support."""
|
"""Internal handler for the bash completion support."""
|
||||||
|
@ -35,7 +54,7 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
|
||||||
|
|
||||||
from ._bashcomplete import bashcomplete
|
from ._bashcomplete import bashcomplete
|
||||||
if bashcomplete(cmd, prog_name, complete_var, complete_instr):
|
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):
|
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 '
|
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
|
||||||
'added as subcommand but it in itself is a '
|
'added as subcommand but it in itself is a '
|
||||||
'multi command. ("%s" is a %s within a chained '
|
'multi command. ("%s" is a %s within a chained '
|
||||||
'%s named "%s"). This restriction was supposed to '
|
'%s named "%s").' % (
|
||||||
'be lifted in 6.0 but the fix was flawed. This '
|
|
||||||
'will be fixed in Click 7.0' % (
|
|
||||||
hint, base_command.name, cmd_name,
|
hint, base_command.name, cmd_name,
|
||||||
cmd_name, cmd.__class__.__name__,
|
cmd_name, cmd.__class__.__name__,
|
||||||
base_command.__class__.__name__,
|
base_command.__class__.__name__,
|
||||||
|
@ -372,7 +389,7 @@ class Context(object):
|
||||||
@property
|
@property
|
||||||
def meta(self):
|
def meta(self):
|
||||||
"""This is a dictionary which is shared with all the contexts
|
"""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
|
state here if they need to. It is however the responsibility of
|
||||||
that code to manage this dictionary well.
|
that code to manage this dictionary well.
|
||||||
|
|
||||||
|
@ -481,7 +498,7 @@ class Context(object):
|
||||||
|
|
||||||
def exit(self, code=0):
|
def exit(self, code=0):
|
||||||
"""Exits the application with a given exit code."""
|
"""Exits the application with a given exit code."""
|
||||||
sys.exit(code)
|
raise Exit(code)
|
||||||
|
|
||||||
def get_usage(self):
|
def get_usage(self):
|
||||||
"""Helper method to get formatted usage string for the current
|
"""Helper method to get formatted usage string for the current
|
||||||
|
@ -655,7 +672,7 @@ class BaseCommand(object):
|
||||||
name from ``sys.argv[0]``.
|
name from ``sys.argv[0]``.
|
||||||
:param complete_var: the environment variable that controls the
|
:param complete_var: the environment variable that controls the
|
||||||
bash completion support. The default is
|
bash completion support. The default is
|
||||||
``"_<prog_name>_COMPLETE"`` with prog name in
|
``"_<prog_name>_COMPLETE"`` with prog_name in
|
||||||
uppercase.
|
uppercase.
|
||||||
:param standalone_mode: the default behavior is to invoke the script
|
:param standalone_mode: the default behavior is to invoke the script
|
||||||
in standalone mode. Click will then
|
in standalone mode. Click will then
|
||||||
|
@ -670,7 +687,7 @@ class BaseCommand(object):
|
||||||
constructor. See :class:`Context` for more information.
|
constructor. See :class:`Context` for more information.
|
||||||
"""
|
"""
|
||||||
# If we are in Python 3, we will verify that the environment is
|
# 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.
|
# broken script.
|
||||||
if not PY2:
|
if not PY2:
|
||||||
_verify_python3_env()
|
_verify_python3_env()
|
||||||
|
@ -697,6 +714,13 @@ class BaseCommand(object):
|
||||||
rv = self.invoke(ctx)
|
rv = self.invoke(ctx)
|
||||||
if not standalone_mode:
|
if not standalone_mode:
|
||||||
return rv
|
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()
|
ctx.exit()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
echo(file=sys.stderr)
|
echo(file=sys.stderr)
|
||||||
|
@ -711,6 +735,19 @@ class BaseCommand(object):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
raise
|
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:
|
except Abort:
|
||||||
if not standalone_mode:
|
if not standalone_mode:
|
||||||
raise
|
raise
|
||||||
|
@ -743,11 +780,16 @@ class Command(BaseCommand):
|
||||||
shown on the command listing of the parent command.
|
shown on the command listing of the parent command.
|
||||||
:param add_help_option: by default each command registers a ``--help``
|
:param add_help_option: by default each command registers a ``--help``
|
||||||
option. This can be disabled by this parameter.
|
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,
|
def __init__(self, name, context_settings=None, callback=None,
|
||||||
params=None, help=None, epilog=None, short_help=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)
|
BaseCommand.__init__(self, name, context_settings)
|
||||||
#: the callback to execute when the command fires. This might be
|
#: the callback to execute when the command fires. This might be
|
||||||
#: `None` in which case nothing happens.
|
#: `None` in which case nothing happens.
|
||||||
|
@ -756,13 +798,17 @@ class Command(BaseCommand):
|
||||||
#: should show up in the help page and execute. Eager parameters
|
#: should show up in the help page and execute. Eager parameters
|
||||||
#: will automatically be handled before non eager ones.
|
#: will automatically be handled before non eager ones.
|
||||||
self.params = params or []
|
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.help = help
|
||||||
self.epilog = epilog
|
self.epilog = epilog
|
||||||
self.options_metavar = options_metavar
|
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.short_help = short_help
|
||||||
self.add_help_option = add_help_option
|
self.add_help_option = add_help_option
|
||||||
|
self.hidden = hidden
|
||||||
|
self.deprecated = deprecated
|
||||||
|
|
||||||
def get_usage(self, ctx):
|
def get_usage(self, ctx):
|
||||||
formatter = ctx.make_formatter()
|
formatter = ctx.make_formatter()
|
||||||
|
@ -816,8 +862,6 @@ class Command(BaseCommand):
|
||||||
def make_parser(self, ctx):
|
def make_parser(self, ctx):
|
||||||
"""Creates the underlying option parser for this command."""
|
"""Creates the underlying option parser for this command."""
|
||||||
parser = OptionParser(ctx)
|
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):
|
for param in self.get_params(ctx):
|
||||||
param.add_to_parser(parser, ctx)
|
param.add_to_parser(parser, ctx)
|
||||||
return parser
|
return parser
|
||||||
|
@ -830,6 +874,10 @@ class Command(BaseCommand):
|
||||||
self.format_help(ctx, formatter)
|
self.format_help(ctx, formatter)
|
||||||
return formatter.getvalue().rstrip('\n')
|
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):
|
def format_help(self, ctx, formatter):
|
||||||
"""Writes the help into the formatter if it exists.
|
"""Writes the help into the formatter if it exists.
|
||||||
|
|
||||||
|
@ -850,7 +898,14 @@ class Command(BaseCommand):
|
||||||
if self.help:
|
if self.help:
|
||||||
formatter.write_paragraph()
|
formatter.write_paragraph()
|
||||||
with formatter.indentation():
|
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):
|
def format_options(self, ctx, formatter):
|
||||||
"""Writes all the options into the formatter if they exist."""
|
"""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)
|
"""Given a context, this invokes the attached callback (if it exists)
|
||||||
in the right way.
|
in the right way.
|
||||||
"""
|
"""
|
||||||
|
_maybe_show_deprecated_notice(self)
|
||||||
if self.callback is not None:
|
if self.callback is not None:
|
||||||
return ctx.invoke(self.callback, **ctx.params)
|
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
|
"""Extra format methods for multi methods that adds all the commands
|
||||||
after the options.
|
after the options.
|
||||||
"""
|
"""
|
||||||
rows = []
|
commands = []
|
||||||
for subcommand in self.list_commands(ctx):
|
for subcommand in self.list_commands(ctx):
|
||||||
cmd = self.get_command(ctx, subcommand)
|
cmd = self.get_command(ctx, subcommand)
|
||||||
# What is this, the tool lied about a command. Ignore it
|
# What is this, the tool lied about a command. Ignore it
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
continue
|
continue
|
||||||
|
if cmd.hidden:
|
||||||
|
continue
|
||||||
|
|
||||||
help = cmd.short_help or ''
|
commands.append((subcommand, cmd))
|
||||||
rows.append((subcommand, help))
|
|
||||||
|
|
||||||
if rows:
|
# allow for 3 times the default spacing
|
||||||
with formatter.section('Commands'):
|
if len(commands):
|
||||||
formatter.write_dl(rows)
|
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):
|
def parse_args(self, ctx, args):
|
||||||
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||||
|
@ -1216,7 +1282,7 @@ class CommandCollection(MultiCommand):
|
||||||
|
|
||||||
|
|
||||||
class Parameter(object):
|
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
|
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
|
||||||
not supported by design as some of the internals for parsing are
|
not supported by design as some of the internals for parsing are
|
||||||
intentionally not finalized.
|
intentionally not finalized.
|
||||||
|
@ -1261,7 +1327,8 @@ class Parameter(object):
|
||||||
|
|
||||||
def __init__(self, param_decls=None, type=None, required=False,
|
def __init__(self, param_decls=None, type=None, required=False,
|
||||||
default=None, callback=None, nargs=None, metavar=None,
|
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.name, self.opts, self.secondary_opts = \
|
||||||
self._parse_decls(param_decls or (), expose_value)
|
self._parse_decls(param_decls or (), expose_value)
|
||||||
|
|
||||||
|
@ -1284,6 +1351,7 @@ class Parameter(object):
|
||||||
self.is_eager = is_eager
|
self.is_eager = is_eager
|
||||||
self.metavar = metavar
|
self.metavar = metavar
|
||||||
self.envvar = envvar
|
self.envvar = envvar
|
||||||
|
self.autocompletion = autocompletion
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def human_readable_name(self):
|
def human_readable_name(self):
|
||||||
|
@ -1314,12 +1382,13 @@ class Parameter(object):
|
||||||
def add_to_parser(self, parser, ctx):
|
def add_to_parser(self, parser, ctx):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def consume_value(self, ctx, opts):
|
def consume_value(self, ctx, opts):
|
||||||
value = opts.get(self.name)
|
value = opts.get(self.name)
|
||||||
if value is None:
|
|
||||||
value = ctx.lookup_default(self.name)
|
|
||||||
if value is None:
|
if value is None:
|
||||||
value = self.value_from_envvar(ctx)
|
value = self.value_from_envvar(ctx)
|
||||||
|
if value is None:
|
||||||
|
value = ctx.lookup_default(self.name)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def type_cast_value(self, ctx, value):
|
def type_cast_value(self, ctx, value):
|
||||||
|
@ -1416,6 +1485,13 @@ class Parameter(object):
|
||||||
def get_usage_pieces(self, ctx):
|
def get_usage_pieces(self, ctx):
|
||||||
return []
|
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):
|
class Option(Parameter):
|
||||||
"""Options are usually optional values on the command line and
|
"""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.
|
All other parameters are passed onwards to the parameter constructor.
|
||||||
|
|
||||||
:param show_default: controls if the default value should be shown on the
|
:param show_default: controls if the default value should be shown on the
|
||||||
help page. Normally, defaults are not shown.
|
help page. Normally, defaults are not shown. If this
|
||||||
:param prompt: if set to `True` or a non empty string then the user will
|
value is a string, it shows the string instead of the
|
||||||
be prompted for input if not set. If set to `True` the
|
value. This is particularly useful for dynamic options.
|
||||||
prompt will be the option name capitalized.
|
: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
|
:param confirmation_prompt: if set then the value will need to be confirmed
|
||||||
if it was prompted for.
|
if it was prompted for.
|
||||||
:param hide_input: if this is `True` then the input on the prompt will be
|
:param hide_input: if this is `True` then the input on the prompt will be
|
||||||
|
@ -1448,6 +1529,7 @@ class Option(Parameter):
|
||||||
variable in case a prefix is defined on the
|
variable in case a prefix is defined on the
|
||||||
context.
|
context.
|
||||||
:param help: the help string.
|
:param help: the help string.
|
||||||
|
:param hidden: hide this option from help outputs.
|
||||||
"""
|
"""
|
||||||
param_type_name = 'option'
|
param_type_name = 'option'
|
||||||
|
|
||||||
|
@ -1455,7 +1537,8 @@ class Option(Parameter):
|
||||||
prompt=False, confirmation_prompt=False,
|
prompt=False, confirmation_prompt=False,
|
||||||
hide_input=False, is_flag=None, flag_value=None,
|
hide_input=False, is_flag=None, flag_value=None,
|
||||||
multiple=False, count=False, allow_from_autoenv=True,
|
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
|
default_is_missing = attrs.get('default', _missing) is _missing
|
||||||
Parameter.__init__(self, param_decls, type=type, **attrs)
|
Parameter.__init__(self, param_decls, type=type, **attrs)
|
||||||
|
|
||||||
|
@ -1468,6 +1551,7 @@ class Option(Parameter):
|
||||||
self.prompt = prompt_text
|
self.prompt = prompt_text
|
||||||
self.confirmation_prompt = confirmation_prompt
|
self.confirmation_prompt = confirmation_prompt
|
||||||
self.hide_input = hide_input
|
self.hide_input = hide_input
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
# Flags
|
# Flags
|
||||||
if is_flag is None:
|
if is_flag is None:
|
||||||
|
@ -1500,6 +1584,8 @@ class Option(Parameter):
|
||||||
self.allow_from_autoenv = allow_from_autoenv
|
self.allow_from_autoenv = allow_from_autoenv
|
||||||
self.help = help
|
self.help = help
|
||||||
self.show_default = show_default
|
self.show_default = show_default
|
||||||
|
self.show_choices = show_choices
|
||||||
|
self.show_envvar = show_envvar
|
||||||
|
|
||||||
# Sanity check for stuff we don't support
|
# Sanity check for stuff we don't support
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
@ -1548,8 +1634,8 @@ class Option(Parameter):
|
||||||
opts.append(decl)
|
opts.append(decl)
|
||||||
|
|
||||||
if name is None and possible_names:
|
if name is None and possible_names:
|
||||||
possible_names.sort(key=lambda x: len(x[0]))
|
possible_names.sort(key=lambda x: -len(x[0])) # group long options first
|
||||||
name = possible_names[-1][1].replace('-', '_').lower()
|
name = possible_names[0][1].replace('-', '_').lower()
|
||||||
if not isidentifier(name):
|
if not isidentifier(name):
|
||||||
name = None
|
name = None
|
||||||
|
|
||||||
|
@ -1595,6 +1681,8 @@ class Option(Parameter):
|
||||||
parser.add_option(self.opts, **kwargs)
|
parser.add_option(self.opts, **kwargs)
|
||||||
|
|
||||||
def get_help_record(self, ctx):
|
def get_help_record(self, ctx):
|
||||||
|
if self.hidden:
|
||||||
|
return
|
||||||
any_prefix_is_slash = []
|
any_prefix_is_slash = []
|
||||||
|
|
||||||
def _write_opts(opts):
|
def _write_opts(opts):
|
||||||
|
@ -1611,11 +1699,28 @@ class Option(Parameter):
|
||||||
|
|
||||||
help = self.help or ''
|
help = self.help or ''
|
||||||
extra = []
|
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:
|
if self.default is not None and self.show_default:
|
||||||
extra.append('default: %s' % (
|
if isinstance(self.show_default, string_types):
|
||||||
', '.join('%s' % d for d in self.default)
|
default_string = '({})'.format(self.show_default)
|
||||||
if isinstance(self.default, (list, tuple))
|
elif isinstance(self.default, (list, tuple)):
|
||||||
else self.default, ))
|
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:
|
if self.required:
|
||||||
extra.append('required')
|
extra.append('required')
|
||||||
if extra:
|
if extra:
|
||||||
|
@ -1649,8 +1754,8 @@ class Option(Parameter):
|
||||||
if self.is_bool_flag:
|
if self.is_bool_flag:
|
||||||
return confirm(self.prompt, default)
|
return confirm(self.prompt, default)
|
||||||
|
|
||||||
return prompt(self.prompt, default=default,
|
return prompt(self.prompt, default=default, type=self.type,
|
||||||
hide_input=self.hide_input,
|
hide_input=self.hide_input, show_choices=self.show_choices,
|
||||||
confirmation_prompt=self.confirmation_prompt,
|
confirmation_prompt=self.confirmation_prompt,
|
||||||
value_proc=lambda x: self.process_value(ctx, x))
|
value_proc=lambda x: self.process_value(ctx, x))
|
||||||
|
|
||||||
|
@ -1710,7 +1815,9 @@ class Argument(Parameter):
|
||||||
def make_metavar(self):
|
def make_metavar(self):
|
||||||
if self.metavar is not None:
|
if self.metavar is not None:
|
||||||
return self.metavar
|
return self.metavar
|
||||||
var = self.name.upper()
|
var = self.type.get_metavar(self)
|
||||||
|
if not var:
|
||||||
|
var = self.name.upper()
|
||||||
if not self.required:
|
if not self.required:
|
||||||
var = '[%s]' % var
|
var = '[%s]' % var
|
||||||
if self.nargs != 1:
|
if self.nargs != 1:
|
||||||
|
@ -1735,6 +1842,9 @@ class Argument(Parameter):
|
||||||
def get_usage_pieces(self, ctx):
|
def get_usage_pieces(self, ctx):
|
||||||
return [self.make_metavar()]
|
return [self.make_metavar()]
|
||||||
|
|
||||||
|
def get_error_hint(self, ctx):
|
||||||
|
return '"%s"' % self.make_metavar()
|
||||||
|
|
||||||
def add_to_parser(self, parser, ctx):
|
def add_to_parser(self, parser, ctx):
|
||||||
parser.add_argument(dest=self.name, nargs=self.nargs,
|
parser.add_argument(dest=self.name, nargs=self.nargs,
|
||||||
obj=self)
|
obj=self)
|
||||||
|
|
|
@ -61,7 +61,7 @@ def make_pass_decorator(object_type, ensure=False):
|
||||||
raise RuntimeError('Managed to invoke callback without a '
|
raise RuntimeError('Managed to invoke callback without a '
|
||||||
'context object of type %r existing'
|
'context object of type %r existing'
|
||||||
% object_type.__name__)
|
% 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 update_wrapper(new_func, f)
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@ -85,12 +85,12 @@ def _make_command(f, name, attrs, cls):
|
||||||
help = inspect.cleandoc(help)
|
help = inspect.cleandoc(help)
|
||||||
attrs['help'] = help
|
attrs['help'] = help
|
||||||
_check_for_unicode_literals()
|
_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)
|
callback=f, params=params, **attrs)
|
||||||
|
|
||||||
|
|
||||||
def command(name=None, cls=None, **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
|
callback. This will also automatically attach all decorated
|
||||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ def command(name=None, cls=None, **attrs):
|
||||||
command :class:`Group`.
|
command :class:`Group`.
|
||||||
|
|
||||||
:param name: the name of the command. This defaults to the function
|
: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
|
:param cls: the command class to instantiate. This defaults to
|
||||||
:class:`Command`.
|
:class:`Command`.
|
||||||
"""
|
"""
|
||||||
|
@ -164,10 +164,13 @@ def option(*param_decls, **attrs):
|
||||||
:class:`Option`.
|
:class:`Option`.
|
||||||
"""
|
"""
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
if 'help' in attrs:
|
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||||
attrs['help'] = inspect.cleandoc(attrs['help'])
|
option_attrs = attrs.copy()
|
||||||
OptionClass = attrs.pop('cls', Option)
|
|
||||||
_param_memo(f, OptionClass(param_decls, **attrs))
|
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 f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@ -235,7 +238,11 @@ def version_option(version=None, *param_decls, **attrs):
|
||||||
:param others: everything else is forwarded to :func:`option`.
|
:param others: everything else is forwarded to :func:`option`.
|
||||||
"""
|
"""
|
||||||
if version is None:
|
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):
|
def decorator(f):
|
||||||
prog_name = attrs.pop('prog_name', None)
|
prog_name = attrs.pop('prog_name', None)
|
||||||
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
||||||
|
|
|
@ -2,6 +2,12 @@ from ._compat import PY2, filename_to_ui, get_text_stderr
|
||||||
from .utils import echo
|
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):
|
class ClickException(Exception):
|
||||||
"""An exception that Click can handle and show to the user."""
|
"""An exception that Click can handle and show to the user."""
|
||||||
|
|
||||||
|
@ -9,15 +15,25 @@ class ClickException(Exception):
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
|
ctor_msg = message
|
||||||
if PY2:
|
if PY2:
|
||||||
if message is not None:
|
if ctor_msg is not None:
|
||||||
message = message.encode('utf-8')
|
ctor_msg = ctor_msg.encode('utf-8')
|
||||||
Exception.__init__(self, message)
|
Exception.__init__(self, ctor_msg)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
def format_message(self):
|
def format_message(self):
|
||||||
return self.message
|
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):
|
def show(self, file=None):
|
||||||
if file is None:
|
if file is None:
|
||||||
file = get_text_stderr()
|
file = get_text_stderr()
|
||||||
|
@ -37,14 +53,20 @@ class UsageError(ClickException):
|
||||||
def __init__(self, message, ctx=None):
|
def __init__(self, message, ctx=None):
|
||||||
ClickException.__init__(self, message)
|
ClickException.__init__(self, message)
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
|
self.cmd = self.ctx and self.ctx.command or None
|
||||||
|
|
||||||
def show(self, file=None):
|
def show(self, file=None):
|
||||||
if file is None:
|
if file is None:
|
||||||
file = get_text_stderr()
|
file = get_text_stderr()
|
||||||
color = None
|
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:
|
if self.ctx is not None:
|
||||||
color = self.ctx.color
|
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)
|
echo('Error: %s' % self.format_message(), file=file, color=color)
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,11 +98,11 @@ class BadParameter(UsageError):
|
||||||
if self.param_hint is not None:
|
if self.param_hint is not None:
|
||||||
param_hint = self.param_hint
|
param_hint = self.param_hint
|
||||||
elif self.param is not None:
|
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:
|
else:
|
||||||
return 'Invalid value: %s' % self.message
|
return 'Invalid value: %s' % self.message
|
||||||
if isinstance(param_hint, (tuple, list)):
|
param_hint = _join_param_hints(param_hint)
|
||||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
|
||||||
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,11 +127,10 @@ class MissingParameter(BadParameter):
|
||||||
if self.param_hint is not None:
|
if self.param_hint is not None:
|
||||||
param_hint = self.param_hint
|
param_hint = self.param_hint
|
||||||
elif self.param is not None:
|
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:
|
else:
|
||||||
param_hint = None
|
param_hint = None
|
||||||
if isinstance(param_hint, (tuple, list)):
|
param_hint = _join_param_hints(param_hint)
|
||||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
|
||||||
|
|
||||||
param_type = self.param_type
|
param_type = self.param_type
|
||||||
if param_type is None and self.param is not None:
|
if param_type is None and self.param is not None:
|
||||||
|
@ -164,10 +185,13 @@ class BadOptionUsage(UsageError):
|
||||||
for an option is not correct.
|
for an option is not correct.
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. 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)
|
UsageError.__init__(self, message, ctx)
|
||||||
|
self.option_name = option_name
|
||||||
|
|
||||||
|
|
||||||
class BadArgumentUsage(UsageError):
|
class BadArgumentUsage(UsageError):
|
||||||
|
@ -199,3 +223,13 @@ class FileError(ClickException):
|
||||||
|
|
||||||
class Abort(RuntimeError):
|
class Abort(RuntimeError):
|
||||||
"""An internal signalling exception that signals Click to abort."""
|
"""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
|
||||||
|
|
|
@ -9,7 +9,7 @@ def get_current_context(silent=False):
|
||||||
access the current context object from anywhere. This is a more implicit
|
access the current context object from anywhere. This is a more implicit
|
||||||
alternative to the :func:`pass_context` decorator. This function is
|
alternative to the :func:`pass_context` decorator. This function is
|
||||||
primarily useful for helpers such as :func:`echo` which might be
|
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.
|
To push the current context, :meth:`Context.scope` can be used.
|
||||||
|
|
||||||
|
|
|
@ -74,8 +74,8 @@ def _unpack_args(args, nargs_spec):
|
||||||
|
|
||||||
def _error_opt_args(nargs, opt):
|
def _error_opt_args(nargs, opt):
|
||||||
if nargs == 1:
|
if nargs == 1:
|
||||||
raise BadOptionUsage('%s option requires an argument' % opt)
|
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
|
||||||
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
|
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
|
||||||
|
|
||||||
|
|
||||||
def split_opt(opt):
|
def split_opt(opt):
|
||||||
|
@ -342,7 +342,7 @@ class OptionParser(object):
|
||||||
del state.rargs[:nargs]
|
del state.rargs[:nargs]
|
||||||
|
|
||||||
elif explicit_value is not None:
|
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:
|
else:
|
||||||
value = None
|
value = None
|
||||||
|
|
111
click/termui.py
|
@ -1,12 +1,14 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
import inspect
|
||||||
|
import itertools
|
||||||
|
|
||||||
from ._compat import raw_input, text_type, string_types, \
|
from ._compat import raw_input, text_type, string_types, \
|
||||||
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
||||||
from .utils import echo
|
from .utils import echo
|
||||||
from .exceptions import Abort, UsageError
|
from .exceptions import Abort, UsageError
|
||||||
from .types import convert_type
|
from .types import convert_type, Choice, Path
|
||||||
from .globals import resolve_color_default
|
from .globals import resolve_color_default
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,8 +16,25 @@ from .globals import resolve_color_default
|
||||||
# functions to customize how they work.
|
# functions to customize how they work.
|
||||||
visible_prompt_func = raw_input
|
visible_prompt_func = raw_input
|
||||||
|
|
||||||
_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta',
|
_ansi_colors = {
|
||||||
'cyan', 'white', 'reset')
|
'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'
|
_ansi_reset_all = '\033[0m'
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,23 +43,27 @@ def hidden_prompt_func(prompt):
|
||||||
return getpass.getpass(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
|
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:
|
if default is not None and show_default:
|
||||||
prompt = '%s [%s]' % (prompt, default)
|
prompt = '%s [%s]' % (prompt, default)
|
||||||
return prompt + suffix
|
return prompt + suffix
|
||||||
|
|
||||||
|
|
||||||
def prompt(text, default=None, hide_input=False,
|
def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
||||||
confirmation_prompt=False, type=None,
|
type=None, value_proc=None, prompt_suffix=': ', show_default=True,
|
||||||
value_proc=None, prompt_suffix=': ',
|
err=False, show_choices=True):
|
||||||
show_default=True, err=False):
|
|
||||||
"""Prompts a user for input. This is a convenience function that can
|
"""Prompts a user for input. This is a convenience function that can
|
||||||
be used to prompt a user for input later.
|
be used to prompt a user for input later.
|
||||||
|
|
||||||
If the user aborts the input by sending a interrupt signal, this
|
If the user aborts the input by sending a interrupt signal, this
|
||||||
function will catch it and raise a :exc:`Abort` exception.
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
|
.. versionadded:: 7.0
|
||||||
|
Added the show_choices parameter.
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
.. versionadded:: 6.0
|
||||||
Added unicode support for cmd.exe on Windows.
|
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 show_default: shows or hides the default value in the prompt.
|
||||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||||
``stdout``, the same as with echo.
|
``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
|
result = None
|
||||||
|
|
||||||
|
@ -82,17 +109,18 @@ def prompt(text, default=None, hide_input=False,
|
||||||
if value_proc is None:
|
if value_proc is None:
|
||||||
value_proc = convert_type(type, default)
|
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:
|
||||||
while 1:
|
while 1:
|
||||||
value = prompt_func(prompt)
|
value = prompt_func(prompt)
|
||||||
if value:
|
if value:
|
||||||
break
|
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:
|
elif default is not None:
|
||||||
|
if isinstance(value_proc, Path):
|
||||||
|
# validate Path default value(exists, dir_okay etc.)
|
||||||
|
value = default
|
||||||
|
break
|
||||||
return default
|
return default
|
||||||
try:
|
try:
|
||||||
result = value_proc(value)
|
result = value_proc(value)
|
||||||
|
@ -166,8 +194,14 @@ def get_terminal_size():
|
||||||
sz = shutil_get_terminal_size()
|
sz = shutil_get_terminal_size()
|
||||||
return sz.columns, sz.lines
|
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:
|
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):
|
def ioctl_gwinsz(fd):
|
||||||
try:
|
try:
|
||||||
|
@ -195,22 +229,33 @@ def get_terminal_size():
|
||||||
return int(cr[1]), int(cr[0])
|
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
|
"""This function takes a text and shows it via an environment specific
|
||||||
pager on stdout.
|
pager on stdout.
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
.. versionchanged:: 3.0
|
||||||
Added the `color` flag.
|
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
|
:param color: controls if the pager supports ANSI colors or not. The
|
||||||
default is autodetection.
|
default is autodetection.
|
||||||
"""
|
"""
|
||||||
color = resolve_color_default(color)
|
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
|
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,
|
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``
|
* ``magenta``
|
||||||
* ``cyan``
|
* ``cyan``
|
||||||
* ``white`` (might be light gray)
|
* ``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)
|
* ``reset`` (reset the color code only)
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. versionadded:: 7.0
|
||||||
|
Added support for bright colors.
|
||||||
|
|
||||||
:param text: the string to style with ansi codes.
|
:param text: the string to style with ansi codes.
|
||||||
:param fg: if provided this will become the foreground color.
|
:param fg: if provided this will become the foreground color.
|
||||||
:param bg: if provided this will become the background 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 = []
|
bits = []
|
||||||
if fg:
|
if fg:
|
||||||
try:
|
try:
|
||||||
bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30))
|
bits.append('\033[%dm' % (_ansi_colors[fg]))
|
||||||
except ValueError:
|
except KeyError:
|
||||||
raise TypeError('Unknown color %r' % fg)
|
raise TypeError('Unknown color %r' % fg)
|
||||||
if bg:
|
if bg:
|
||||||
try:
|
try:
|
||||||
bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40))
|
bits.append('\033[%dm' % (_ansi_colors[bg] + 10))
|
||||||
except ValueError:
|
except KeyError:
|
||||||
raise TypeError('Unknown color %r' % bg)
|
raise TypeError('Unknown color %r' % bg)
|
||||||
if bold is not None:
|
if bold is not None:
|
||||||
bits.append('\033[%dm' % (1 if bold else 22))
|
bits.append('\033[%dm' % (1 if bold else 22))
|
||||||
|
@ -405,7 +461,7 @@ def unstyle(text):
|
||||||
return strip_ansi(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
|
"""This function combines :func:`echo` and :func:`style` into one
|
||||||
call. As such the following two calls are the same::
|
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
|
.. 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,
|
def edit(text=None, editor=None, env=None, require_save=True,
|
||||||
|
@ -510,6 +568,11 @@ def getchar(echo=False):
|
||||||
return f(echo)
|
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):
|
def pause(info='Press any key to continue ...', err=False):
|
||||||
"""This command stops execution and waits for the user to press any
|
"""This command stops execution and waits for the user to press any
|
||||||
key to continue. This is similar to the Windows batch "pause"
|
key to continue. This is similar to the Windows batch "pause"
|
||||||
|
|
102
click/testing.py
|
@ -3,8 +3,9 @@ import sys
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import contextlib
|
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
|
# If someone wants to vendor click, we want to ensure the
|
||||||
|
@ -72,27 +73,44 @@ def make_input_stream(input, charset):
|
||||||
class Result(object):
|
class Result(object):
|
||||||
"""Holds the captured result of an invoked CLI script."""
|
"""Holds the captured result of an invoked CLI script."""
|
||||||
|
|
||||||
def __init__(self, runner, output_bytes, exit_code, exception,
|
def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code,
|
||||||
exc_info=None):
|
exception, exc_info=None):
|
||||||
#: The runner that created the result
|
#: The runner that created the result
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
#: The output as bytes.
|
#: The standard output as bytes.
|
||||||
self.output_bytes = output_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.
|
#: The exit code as integer.
|
||||||
self.exit_code = exit_code
|
self.exit_code = exit_code
|
||||||
#: The exception that happend if one did.
|
#: The exception that happened if one did.
|
||||||
self.exception = exception
|
self.exception = exception
|
||||||
#: The traceback
|
#: The traceback
|
||||||
self.exc_info = exc_info
|
self.exc_info = exc_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def output(self):
|
def output(self):
|
||||||
"""The output as unicode string."""
|
"""The (standard) output as unicode string."""
|
||||||
return self.output_bytes.decode(self.runner.charset, 'replace') \
|
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')
|
.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):
|
def __repr__(self):
|
||||||
return '<Result %s>' % (
|
return '<%s %s>' % (
|
||||||
|
type(self).__name__,
|
||||||
self.exception and repr(self.exception) or 'okay',
|
self.exception and repr(self.exception) or 'okay',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -111,14 +129,21 @@ class CliRunner(object):
|
||||||
to stdout. This is useful for showing examples in
|
to stdout. This is useful for showing examples in
|
||||||
some circumstances. Note that regular prompts
|
some circumstances. Note that regular prompts
|
||||||
will automatically echo the input.
|
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:
|
if charset is None:
|
||||||
charset = 'utf-8'
|
charset = 'utf-8'
|
||||||
self.charset = charset
|
self.charset = charset
|
||||||
self.env = env or {}
|
self.env = env or {}
|
||||||
self.echo_stdin = echo_stdin
|
self.echo_stdin = echo_stdin
|
||||||
|
self.mix_stderr = mix_stderr
|
||||||
|
|
||||||
def get_default_prog_name(self, cli):
|
def get_default_prog_name(self, cli):
|
||||||
"""Given a command object it will return the default program name
|
"""Given a command object it will return the default program name
|
||||||
|
@ -163,16 +188,27 @@ class CliRunner(object):
|
||||||
env = self.make_env(env)
|
env = self.make_env(env)
|
||||||
|
|
||||||
if PY2:
|
if PY2:
|
||||||
sys.stdout = sys.stderr = bytes_output = StringIO()
|
bytes_output = StringIO()
|
||||||
if self.echo_stdin:
|
if self.echo_stdin:
|
||||||
input = EchoingStdin(input, bytes_output)
|
input = EchoingStdin(input, bytes_output)
|
||||||
|
sys.stdout = bytes_output
|
||||||
|
if not self.mix_stderr:
|
||||||
|
bytes_error = StringIO()
|
||||||
|
sys.stderr = bytes_error
|
||||||
else:
|
else:
|
||||||
bytes_output = io.BytesIO()
|
bytes_output = io.BytesIO()
|
||||||
if self.echo_stdin:
|
if self.echo_stdin:
|
||||||
input = EchoingStdin(input, bytes_output)
|
input = EchoingStdin(input, bytes_output)
|
||||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||||
sys.stdout = sys.stderr = io.TextIOWrapper(
|
sys.stdout = io.TextIOWrapper(
|
||||||
bytes_output, encoding=self.charset)
|
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
|
sys.stdin = input
|
||||||
|
|
||||||
|
@ -196,6 +232,7 @@ class CliRunner(object):
|
||||||
return char
|
return char
|
||||||
|
|
||||||
default_color = color
|
default_color = color
|
||||||
|
|
||||||
def should_strip_ansi(stream=None, color=None):
|
def should_strip_ansi(stream=None, color=None):
|
||||||
if color is None:
|
if color is None:
|
||||||
return not default_color
|
return not default_color
|
||||||
|
@ -221,7 +258,7 @@ class CliRunner(object):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
os.environ[key] = value
|
os.environ[key] = value
|
||||||
yield bytes_output
|
yield (bytes_output, not self.mix_stderr and bytes_error)
|
||||||
finally:
|
finally:
|
||||||
for key, value in iteritems(old_env):
|
for key, value in iteritems(old_env):
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -241,7 +278,7 @@ class CliRunner(object):
|
||||||
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
||||||
|
|
||||||
def invoke(self, cli, args=None, input=None, env=None,
|
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
|
"""Invokes a command in an isolated environment. The arguments are
|
||||||
forwarded directly to the command line script, the `extra` keyword
|
forwarded directly to the command line script, the `extra` keyword
|
||||||
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
||||||
|
@ -260,7 +297,10 @@ class CliRunner(object):
|
||||||
The ``color`` parameter was added.
|
The ``color`` parameter was added.
|
||||||
|
|
||||||
:param cli: the command to invoke
|
: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 input: the input data for `sys.stdin`.
|
||||||
:param env: the environment overrides.
|
:param env: the environment overrides.
|
||||||
:param catch_exceptions: Whether to catch any other exceptions than
|
:param catch_exceptions: Whether to catch any other exceptions than
|
||||||
|
@ -270,36 +310,48 @@ class CliRunner(object):
|
||||||
application can still override this explicitly.
|
application can still override this explicitly.
|
||||||
"""
|
"""
|
||||||
exc_info = None
|
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
|
exception = None
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
|
|
||||||
|
if isinstance(args, string_types):
|
||||||
|
args = shlex.split(args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cli.main(args=args or (),
|
prog_name = extra.pop("prog_name")
|
||||||
prog_name=self.get_default_prog_name(cli), **extra)
|
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:
|
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
|
exception = e
|
||||||
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
|
|
||||||
exit_code = e.code
|
|
||||||
if not isinstance(exit_code, int):
|
if not isinstance(exit_code, int):
|
||||||
sys.stdout.write(str(exit_code))
|
sys.stdout.write(str(exit_code))
|
||||||
sys.stdout.write('\n')
|
sys.stdout.write('\n')
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not catch_exceptions:
|
if not catch_exceptions:
|
||||||
raise
|
raise
|
||||||
exception = e
|
exception = e
|
||||||
exit_code = -1
|
exit_code = 1
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
finally:
|
finally:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
output = out.getvalue()
|
stdout = outstreams[0].getvalue()
|
||||||
|
stderr = outstreams[1] and outstreams[1].getvalue()
|
||||||
|
|
||||||
return Result(runner=self,
|
return Result(runner=self,
|
||||||
output_bytes=output,
|
stdout_bytes=stdout,
|
||||||
|
stderr_bytes=stderr,
|
||||||
exit_code=exit_code,
|
exit_code=exit_code,
|
||||||
exception=exception,
|
exception=exception,
|
||||||
exc_info=exc_info)
|
exc_info=exc_info)
|
||||||
|
|
136
click/types.py
|
@ -126,34 +126,54 @@ class StringParamType(ParamType):
|
||||||
|
|
||||||
|
|
||||||
class Choice(ParamType):
|
class Choice(ParamType):
|
||||||
"""The choice type allows a value to be checked against a fixed set of
|
"""The choice type allows a value to be checked against a fixed set
|
||||||
supported values. All of these values have to be strings.
|
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.
|
See :ref:`choice-opts` for an example.
|
||||||
|
|
||||||
|
:param case_sensitive: Set to false to make choices case
|
||||||
|
insensitive. Defaults to true.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'choice'
|
name = 'choice'
|
||||||
|
|
||||||
def __init__(self, choices):
|
def __init__(self, choices, case_sensitive=True):
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
|
self.case_sensitive = case_sensitive
|
||||||
|
|
||||||
def get_metavar(self, param):
|
def get_metavar(self, param):
|
||||||
return '[%s]' % '|'.join(self.choices)
|
return '[%s]' % '|'.join(self.choices)
|
||||||
|
|
||||||
def get_missing_message(self, param):
|
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):
|
def convert(self, value, param, ctx):
|
||||||
# Exact match
|
# Exact match
|
||||||
if value in self.choices:
|
if value in self.choices:
|
||||||
return value
|
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 \
|
if ctx is not None and \
|
||||||
ctx.token_normalize_func is not None:
|
ctx.token_normalize_func is not None:
|
||||||
value = ctx.token_normalize_func(value)
|
normed_value = ctx.token_normalize_func(value)
|
||||||
for choice in self.choices:
|
normed_choices = [ctx.token_normalize_func(choice) for choice in
|
||||||
if ctx.token_normalize_func(choice) == value:
|
self.choices]
|
||||||
return choice
|
|
||||||
|
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)' %
|
self.fail('invalid choice: %s. (choose from %s)' %
|
||||||
(value, ', '.join(self.choices)), param, ctx)
|
(value, ', '.join(self.choices)), param, ctx)
|
||||||
|
@ -214,23 +234,6 @@ class IntRange(IntParamType):
|
||||||
return 'IntRange(%r, %r)' % (self.min, self.max)
|
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):
|
class FloatParamType(ParamType):
|
||||||
name = 'float'
|
name = 'float'
|
||||||
|
|
||||||
|
@ -245,6 +248,62 @@ class FloatParamType(ParamType):
|
||||||
return 'FLOAT'
|
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):
|
class UUIDParameterType(ParamType):
|
||||||
name = 'uuid'
|
name = 'uuid'
|
||||||
|
|
||||||
|
@ -273,9 +332,12 @@ class File(ParamType):
|
||||||
opened in binary mode or for writing. The encoding parameter can be used
|
opened in binary mode or for writing. The encoding parameter can be used
|
||||||
to force a specific encoding.
|
to force a specific encoding.
|
||||||
|
|
||||||
The `lazy` flag controls if the file should be opened immediately or
|
The `lazy` flag controls if the file should be opened immediately or upon
|
||||||
upon first IO. The default is to be non lazy for standard input and
|
first IO. The default is to be non-lazy for standard input and output
|
||||||
output streams as well as files opened for reading, lazy otherwise.
|
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
|
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
|
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 readable: if true, a readable check is performed.
|
||||||
:param resolve_path: if this is true, then the path is fully resolved
|
:param resolve_path: if this is true, then the path is fully resolved
|
||||||
before the value is passed onwards. This means
|
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
|
:param allow_dash: If this is set to `True`, a single dash to indicate
|
||||||
standard streams is permitted.
|
standard streams is permitted.
|
||||||
:param type: optionally a string type that should be used to
|
:param path_type: optionally a string type that should be used to
|
||||||
represent the path. The default is `None` which
|
represent the path. The default is `None` which
|
||||||
means the return value will be either bytes or
|
means the return value will be either bytes or
|
||||||
unicode depending on what makes most sense given the
|
unicode depending on what makes most sense given the
|
||||||
input data Click deals with.
|
input data Click deals with.
|
||||||
"""
|
"""
|
||||||
envvar_list_splitter = os.path.pathsep
|
envvar_list_splitter = os.path.pathsep
|
||||||
|
|
||||||
|
@ -384,7 +448,7 @@ class Path(ParamType):
|
||||||
if self.file_okay and not self.dir_okay:
|
if self.file_okay and not self.dir_okay:
|
||||||
self.name = 'file'
|
self.name = 'file'
|
||||||
self.path_type = '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.name = 'directory'
|
||||||
self.path_type = 'Directory'
|
self.path_type = 'Directory'
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -43,6 +43,7 @@ def make_str(value):
|
||||||
|
|
||||||
|
|
||||||
def make_default_short_help(help, max_length=45):
|
def make_default_short_help(help, max_length=45):
|
||||||
|
"""Return a condensed version of help string."""
|
||||||
words = help.split()
|
words = help.split()
|
||||||
total_length = 0
|
total_length = 0
|
||||||
result = []
|
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
|
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
|
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
|
best to not fail. As of Click 6.0 this includes support for unicode
|
||||||
output on the Windows console.
|
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
|
- hide ANSI codes automatically if the destination file is not a
|
||||||
terminal.
|
terminal.
|
||||||
|
|
||||||
.. _colorama: http://pypi.python.org/pypi/colorama
|
.. _colorama: https://pypi.org/project/colorama/
|
||||||
|
|
||||||
.. versionchanged:: 6.0
|
.. versionchanged:: 6.0
|
||||||
As of Click 6.0 the echo function will properly support unicode
|
As of Click 6.0 the echo function will properly support unicode
|
||||||
|
|
130
docs/Makefile
|
@ -1,130 +1,20 @@
|
||||||
# Makefile for Sphinx documentation
|
# Minimal makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS =
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
PAPER =
|
SPHINXPROJ = Jinja
|
||||||
|
SOURCEDIR = .
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
||||||
# Internal variables.
|
# Put it first so that "make" without argument is like "make help".
|
||||||
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
|
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
@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)"
|
|
||||||
|
|
||||||
clean:
|
.PHONY: help Makefile
|
||||||
-rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
html:
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
@echo
|
%: Makefile
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
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."
|
|
||||||
|
|
BIN
docs/_static/click-icon.png
vendored
Normal file
After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
BIN
docs/_static/click-small.png
vendored
Before Width: | Height: | Size: 1.8 KiB |
BIN
docs/_static/click.png
vendored
Before Width: | Height: | Size: 7.8 KiB |
13
docs/_templates/sidebarintro.html
vendored
|
@ -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. It’s 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>
|
|
3
docs/_templates/sidebarlogo.html
vendored
|
@ -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>
|
|
|
@ -277,7 +277,7 @@ options:
|
||||||
If you go with this solution, the extra arguments will be collected in
|
If you go with this solution, the extra arguments will be collected in
|
||||||
:attr:`Context.args`.
|
:attr:`Context.args`.
|
||||||
2. You can attach a :func:`argument` with ``nargs`` set to `-1` which
|
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
|
set the `type` to :data:`UNPROCESSED` to avoid any string processing
|
||||||
on those arguments as otherwise they are forced into unicode strings
|
on those arguments as otherwise they are forced into unicode strings
|
||||||
automatically which is often not what you want.
|
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.option('-v', '--verbose', is_flag=True, help='Enables verbose mode')
|
||||||
@click.argument('timeit_args', nargs=-1, type=click.UNPROCESSED)
|
@click.argument('timeit_args', nargs=-1, type=click.UNPROCESSED)
|
||||||
def cli(verbose, timeit_args):
|
def cli(verbose, timeit_args):
|
||||||
"""A wrapper around Python's timeit."""
|
"""A fake wrapper around Python's timeit."""
|
||||||
cmdline = ['python', '-mtimeit'] + list(timeit_args)
|
cmdline = ['echo', 'python', '-mtimeit'] + list(timeit_args)
|
||||||
if verbose:
|
if verbose:
|
||||||
click.echo('Invoking: %s' % ' '.join(cmdline))
|
click.echo('Invoking: %s' % ' '.join(cmdline))
|
||||||
call(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
|
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
|
if an option will accept an argument or not, the ``bar`` part might be
|
||||||
handled as an argument.
|
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
|
necessary. For instance in the above example there is an option
|
||||||
called ``-v`` which enables verbose mode. If the command would be
|
called ``-v`` which enables verbose mode. If the command would be
|
||||||
ignored with ``-va`` then the ``-v`` part would be handled by Click
|
ignored with ``-va`` then the ``-v`` part would be handled by Click
|
||||||
|
@ -346,7 +346,7 @@ Global Context Access
|
||||||
.. versionadded:: 5.0
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
Starting with Click 5.0 it is possible to access the current context from
|
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
|
:func:`get_current_context` function which returns it. This is primarily
|
||||||
useful for accessing the context bound object as well as some flags that
|
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
|
are stored on it to customize the runtime behavior. For instance the
|
||||||
|
|
|
@ -243,3 +243,21 @@ And from the command line:
|
||||||
.. click:run::
|
.. click:run::
|
||||||
|
|
||||||
invoke(touch, ['--', '-foo.txt', 'bar.txt'])
|
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'])
|
||||||
|
|
||||||
|
|
|
@ -12,17 +12,15 @@ Limitations
|
||||||
|
|
||||||
Bash completion is only available if a script has been installed properly,
|
Bash completion is only available if a script has been installed properly,
|
||||||
and not executed through the ``python`` command. For information about
|
and not executed through the ``python`` command. For information about
|
||||||
how to do that, see :ref:`setuptools-integration`. Also, Click currently
|
how to do that, see :ref:`setuptools-integration`. Click currently
|
||||||
only supports completion for Bash.
|
only supports completion for Bash and Zsh.
|
||||||
|
|
||||||
Currently, Bash completion is an internal feature that is not customizable.
|
|
||||||
This might be relaxed in future versions.
|
|
||||||
|
|
||||||
What it Completes
|
What it Completes
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Generally, the Bash completion support will complete subcommands and
|
Generally, the Bash completion support will complete subcommands, options
|
||||||
parameters. Subcommands are always listed whereas parameters only if at
|
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::
|
least a dash has been provided. Example::
|
||||||
|
|
||||||
$ repo <TAB><TAB>
|
$ repo <TAB><TAB>
|
||||||
|
@ -30,6 +28,60 @@ least a dash has been provided. Example::
|
||||||
$ repo clone -<TAB><TAB>
|
$ repo clone -<TAB><TAB>
|
||||||
--deep --help --rev --shallow -r
|
--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
|
Activation
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -42,14 +94,18 @@ with dashes replaced by underscores.
|
||||||
|
|
||||||
If your tool is called ``foo-bar``, then the magic variable is called
|
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
|
``_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
|
For instance, to enable Bash completion for your ``foo-bar`` script, this
|
||||||
is what you would need to put into your ``.bashrc``::
|
is what you would need to put into your ``.bashrc``::
|
||||||
|
|
||||||
eval "$(_FOO_BAR_COMPLETE=source foo-bar)"
|
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
|
Activation Script
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -64,6 +120,12 @@ This can be easily accomplished::
|
||||||
|
|
||||||
_FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh
|
_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
|
. /path/to/foo-bar-complete.sh
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
.. currentmodule:: click
|
.. currentmodule:: click
|
||||||
|
|
||||||
.. include:: ../CHANGES
|
.. include:: ../CHANGES.rst
|
||||||
|
|
|
@ -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)
|
|
|
@ -29,7 +29,7 @@ when an inner command runs:
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def sync():
|
def sync():
|
||||||
click.echo('Synching')
|
click.echo('Syncing')
|
||||||
|
|
||||||
Here is what this looks like:
|
Here is what this looks like:
|
||||||
|
|
||||||
|
@ -87,6 +87,10 @@ script like this:
|
||||||
@click.option('--debug/--no-debug', default=False)
|
@click.option('--debug/--no-debug', default=False)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, debug):
|
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
|
ctx.obj['DEBUG'] = debug
|
||||||
|
|
||||||
@cli.command()
|
@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
|
For a more complex example that also improves upon handling of the
|
||||||
pipelines have a look at the `imagepipe multi command chaining demo
|
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
|
the Click repository. It implements a pipeline based image editing tool
|
||||||
that has a nice internal structure for the pipelines.
|
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
|
The default map can be nested arbitrarily for each subcommand and
|
||||||
provided when the script is invoked. Alternatively, it can also be
|
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.
|
load the defaults from a configuration file.
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
|
@ -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, but then a command needs to be aware that it's attached below such a
|
||||||
plugin.
|
plugin.
|
||||||
|
|
||||||
There is a much better system that can built by taking advantage of the linked
|
There is a much better system that can be built by taking advantage of the
|
||||||
nature of contexts. We know that the plugin context is linked to the context
|
linked nature of contexts. We know that the plugin context is linked to the
|
||||||
that created our repo. Because of that, we can start a search for the last
|
context that created our repo. Because of that, we can start a search for
|
||||||
level where the object stored by the context was a repo.
|
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`
|
Built-in support for this is provided by the :func:`make_pass_decorator`
|
||||||
factory, which will create decorators for us that find objects (it
|
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
|
``Repo`` object and stored it in the context. For some more advanced use
|
||||||
cases, this might become a problem. The default behavior of
|
cases, this might become a problem. The default behavior of
|
||||||
:func:`make_pass_decorator` is to call :meth:`Context.find_object`
|
: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
|
which will find the object. If it can't find the object,
|
||||||
error. The alternative behavior is to use :meth:`Context.ensure_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
|
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
|
store it in the innermost context. This behavior can also be enabled for
|
||||||
:func:`make_pass_decorator` by passing ``ensure=True``:
|
:func:`make_pass_decorator` by passing ``ensure=True``:
|
||||||
|
@ -210,7 +211,7 @@ As such it runs standalone:
|
||||||
@click.command()
|
@click.command()
|
||||||
@pass_repo
|
@pass_repo
|
||||||
def cp(repo):
|
def cp(repo):
|
||||||
click.echo(repo)
|
click.echo(isinstance(repo, Repo))
|
||||||
|
|
||||||
As you can see:
|
As you can see:
|
||||||
|
|
||||||
|
|
238
docs/conf.py
|
@ -1,219 +1,47 @@
|
||||||
# -*- coding: utf-8 -*-
|
from pallets_sphinx_themes import ProjectLink, get_version
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import sys, os
|
# Project --------------------------------------------------------------
|
||||||
import datetime
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
project = "Click"
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
copyright = "2014 Pallets Team"
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
author = "Pallets Team"
|
||||||
sys.path.append(os.path.abspath('..'))
|
release, version = get_version("Click")
|
||||||
sys.path.append(os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# General --------------------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
master_doc = "index"
|
||||||
#needs_sphinx = '1.0'
|
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
|
# HTML -----------------------------------------------------------------
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
|
|
||||||
'clickdoctools']
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
html_theme = "click"
|
||||||
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_options = {
|
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 = {
|
html_sidebars = {
|
||||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
"index": ["project.html", "versions.html", "searchbox.html"],
|
||||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
"**": ["localtoc.html", "relations.html", "versions.html", "searchbox.html"],
|
||||||
'sourcelink.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
|
# LaTeX ----------------------------------------------------------------
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# 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 = [
|
latex_documents = [
|
||||||
('index', 'click.tex', u'click documentation',
|
(master_doc, "Click.tex", "Click Documentation", "Pallets Team", "manual")
|
||||||
u'Armin Ronacher', '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
|
|
||||||
}
|
|
||||||
|
|
|
@ -72,6 +72,41 @@ Example:
|
||||||
|
|
||||||
And what it looks like:
|
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::
|
.. click:run::
|
||||||
|
|
||||||
invoke(cli, args=['--help'])
|
invoke(cli, args=['--help'])
|
||||||
|
|
|
@ -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
|
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
|
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
|
pip install click
|
||||||
|
|
||||||
Documentation Contents
|
Documentation
|
||||||
----------------------
|
-------------
|
||||||
|
|
||||||
This part of the documentation guides you through all of the library's
|
This part of the documentation guides you through all of the library's
|
||||||
usage patterns.
|
usage patterns.
|
||||||
|
|
157
docs/make.bat
|
@ -1,155 +1,36 @@
|
||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
if "%SPHINXBUILD%" == "" (
|
||||||
set SPHINXBUILD=sphinx-build
|
set SPHINXBUILD=sphinx-build
|
||||||
)
|
)
|
||||||
|
set SOURCEDIR=.
|
||||||
set BUILDDIR=_build
|
set BUILDDIR=_build
|
||||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
set SPHINXPROJ=Jinja
|
||||||
if NOT "%PAPER%" == "" (
|
|
||||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "" goto help
|
if "%1" == "" goto help
|
||||||
|
|
||||||
if "%1" == "help" (
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
:help
|
if errorlevel 9009 (
|
||||||
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
|
|
||||||
echo.
|
echo.
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
goto end
|
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% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
goto end
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "singlehtml" (
|
:help
|
||||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
:end
|
:end
|
||||||
|
popd
|
||||||
|
|
109
docs/options.rst
|
@ -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
|
are a ton of parameters to configure their behavior. Options in click are
|
||||||
distinct from :ref:`positional arguments <arguments>`.
|
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
|
Basic Value Options
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The most basic option is a value option. These options accept one
|
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
|
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
|
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
|
:data:`STRING`. Unless a name is explicitly specified, the name of the
|
||||||
option defined; otherwise the first short one is used.
|
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::
|
.. click:example::
|
||||||
|
|
||||||
|
@ -26,6 +50,23 @@ option defined; otherwise the first short one is used.
|
||||||
def dots(n):
|
def dots(n):
|
||||||
click.echo('.' * 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:
|
And on the command line:
|
||||||
|
|
||||||
.. click:run::
|
.. 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
|
In this case the option is of type :data:`INT` because the default value
|
||||||
is an integer.
|
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
|
Multi Value Options
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -70,7 +124,7 @@ the tuple. For this you can directly specify a tuple as type:
|
||||||
.. click:example::
|
.. click:example::
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--item', type=(unicode, int))
|
@click.option('--item', type=(str, int))
|
||||||
def putitem(item):
|
def putitem(item):
|
||||||
click.echo('name=%s id=%d' % item)
|
click.echo('name=%s id=%d' % item)
|
||||||
|
|
||||||
|
@ -87,7 +141,7 @@ used. The above example is thus equivalent to this:
|
||||||
.. click:example::
|
.. click:example::
|
||||||
|
|
||||||
@click.command()
|
@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):
|
def putitem(item):
|
||||||
click.echo('name=%s id=%d' % item)
|
click.echo('name=%s id=%d' % item)
|
||||||
|
|
||||||
|
@ -282,6 +336,11 @@ What it looks like:
|
||||||
println()
|
println()
|
||||||
invoke(digest, args=['--help'])
|
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:
|
.. _option-prompting:
|
||||||
|
|
||||||
Prompting
|
Prompting
|
||||||
|
@ -374,6 +433,21 @@ from the environment:
|
||||||
def hello(username):
|
def hello(username):
|
||||||
print("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
|
Callbacks and Eager Options
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
@ -514,6 +588,33 @@ And from the command line:
|
||||||
invoke(greet, env={'GREETER_USERNAME': 'john'},
|
invoke(greet, env={'GREETER_USERNAME': 'john'},
|
||||||
auto_envvar_prefix='GREETER')
|
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
|
The second option is to manually pull values in from specific environment
|
||||||
variables by defining the name of the environment variable on the option.
|
variables by defining the name of the environment variable on the option.
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,8 @@ different behavior and some are supported out of the box:
|
||||||
|
|
||||||
``bool`` / :data:`click.BOOL`:
|
``bool`` / :data:`click.BOOL`:
|
||||||
A parameter that accepts boolean values. This is automatically used
|
A parameter that accepts boolean values. This is automatically used
|
||||||
for boolean flags. If used with string values ``1``, ``yes``, ``y``
|
for boolean flags. If used with string values ``1``, ``yes``, ``y``, ``t``
|
||||||
and ``true`` convert to `True` and ``0``, ``no``, ``n`` and ``false``
|
and ``true`` convert to `True` and ``0``, ``no``, ``n``, ``f`` and ``false``
|
||||||
convert to `False`.
|
convert to `False`.
|
||||||
|
|
||||||
:data:`click.UUID`:
|
:data:`click.UUID`:
|
||||||
|
@ -67,25 +67,41 @@ different behavior and some are supported out of the box:
|
||||||
.. autoclass:: IntRange
|
.. autoclass:: IntRange
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
|
.. autoclass:: FloatRange
|
||||||
|
:noindex:
|
||||||
|
|
||||||
Custom parameter types can be implemented by subclassing
|
Custom parameter types can be implemented by subclassing
|
||||||
:class:`click.ParamType`. For simple cases, passing a Python function that
|
:class:`click.ParamType`. For simple cases, passing a Python function that
|
||||||
fails with a `ValueError` is also supported, though discouraged.
|
fails with a `ValueError` is also supported, though discouraged.
|
||||||
|
|
||||||
|
.. _parameter_names:
|
||||||
|
|
||||||
Parameter Names
|
Parameter Names
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Parameters (both options and arguments) accept a number of positional
|
Parameters (both options and arguments) accept a number of positional arguments
|
||||||
arguments which are the parameter declarations. Each string with a
|
which are passed to the command function as parameters. Each string with a
|
||||||
single dash is added as short argument; each string starting with a double
|
single dash is added as a short argument; each string starting with a double
|
||||||
dash as long one. If a string is added without any dashes, it becomes the
|
dash as a long one.
|
||||||
internal parameter name which is also used as variable name.
|
|
||||||
|
|
||||||
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
|
automatically by taking the longest argument and converting all dashes to
|
||||||
underscores. For an option with ``('-f', '--foo-bar')``, the parameter
|
underscores.
|
||||||
name is `foo_bar`. For an option with ``('-x',)``, the parameter is `x`.
|
|
||||||
For an option with ``('-f', '--filename', 'dest')``, the parameter is
|
The internal name is converted to lowercase.
|
||||||
called `dest`.
|
|
||||||
|
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
|
Implementing Custom Types
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
|
@ -6,9 +6,9 @@ Python 3 Support
|
||||||
Click supports Python 3, but like all other command line utility libraries,
|
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
|
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
|
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.
|
utilities unless Python 3 is a hard requirement.
|
||||||
|
|
||||||
.. _python3-limitations:
|
.. _python3-limitations:
|
||||||
|
|
|
@ -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
|
Once you have it installed, run the ``pip`` command from above, but without
|
||||||
the `sudo` prefix.
|
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
|
Once you have virtualenv installed, just fire up a shell and create
|
||||||
your own environment. I usually create a project folder and a `venv`
|
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
|
$ 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
|
Now, let's move on. Enter the following command to get Click activated in your
|
||||||
virtualenv::
|
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:
|
as in the GitHub repository together with readme files:
|
||||||
|
|
||||||
* ``inout``: `File input and output
|
* ``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
|
* ``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
|
* ``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
|
* ``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
|
* ``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
|
* ``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
|
* ``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
|
* ``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
|
* ``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
|
Click is based on declaring commands through decorators. Internally, there
|
||||||
is a non-decorator interface for advanced use cases, but it's discouraged
|
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
|
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
|
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
|
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 /
|
If you don't need this, you can also use the `print()` construct /
|
||||||
function.
|
function.
|
||||||
|
|
2
docs/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Sphinx
|
||||||
|
Pallets-Sphinx-Themes
|
|
@ -61,6 +61,11 @@ Example::
|
||||||
assert 'Debug mode is on' in result.output
|
assert 'Debug mode is on' in result.output
|
||||||
assert 'Syncing' 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
|
File System Isolation
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ properly by this function.
|
||||||
Multicommand Chaining API
|
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
|
Click internally dispatches. Unfortunately this change was not correctly
|
||||||
implemented and it appeared that it was possible to provide an API that
|
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
|
can inform the super command about all the subcommands that will be
|
||||||
|
|
|
@ -41,7 +41,7 @@ new in Click 6.0.
|
||||||
|
|
||||||
Click now emulates output streams on Windows to support unicode to the
|
Click now emulates output streams on Windows to support unicode to the
|
||||||
Windows console through separate APIs. For more information see
|
Windows console through separate APIs. For more information see
|
||||||
`wincmd`_.
|
:doc:`wincmd`.
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
@ -61,7 +61,8 @@ ANSI Colors
|
||||||
Starting with Click 2.0, the :func:`echo` function gained extra
|
Starting with Click 2.0, the :func:`echo` function gained extra
|
||||||
functionality to deal with ANSI colors and styles. Note that on Windows,
|
functionality to deal with ANSI colors and styles. Note that on Windows,
|
||||||
this functionality is only available if `colorama`_ is installed. If it
|
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:
|
Primarily this means that:
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ a single function called :func:`secho`::
|
||||||
click.secho('ATTENTION', blink=True, bold=True)
|
click.secho('ATTENTION', blink=True, bold=True)
|
||||||
|
|
||||||
|
|
||||||
.. _colorama: https://pypi.python.org/pypi/colorama
|
.. _colorama: https://pypi.org/project/colorama/
|
||||||
|
|
||||||
Pager Support
|
Pager Support
|
||||||
-------------
|
-------------
|
||||||
|
@ -114,6 +115,17 @@ Example:
|
||||||
click.echo_via_pager('\n'.join('Line %d' % idx
|
click.echo_via_pager('\n'.join('Line %d' % idx
|
||||||
for idx in range(200)))
|
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
|
Screen Clearing
|
||||||
---------------
|
---------------
|
||||||
|
@ -226,7 +238,7 @@ Launching Applications
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Click supports launching applications through :func:`launch`. This can be
|
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.
|
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
|
In addition to this, it can also launch the file manager and automatically
|
||||||
select the provided file.
|
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
|
Because of this, click provides the :func:`get_binary_stream` and
|
||||||
:func:`get_text_stream` functions, which produce consistent results with
|
: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
|
The end result is that these functions will always return a functional
|
||||||
stream object (except in very odd cases in Python 3; see
|
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
|
Click now emulates output streams on Windows to support unicode to the
|
||||||
Windows console through separate APIs. For more information see
|
Windows console through separate APIs. For more information see
|
||||||
`wincmd`_.
|
:doc:`wincmd`.
|
||||||
|
|
||||||
|
|
||||||
Intelligent File Opening
|
Intelligent File Opening
|
||||||
|
|
|
@ -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:
|
line utility for Python out there which ticks the following boxes:
|
||||||
|
|
||||||
* is lazily composable without restrictions
|
* 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 loading values from environment variables out of the box
|
||||||
* supports for prompting of custom values
|
* supports for prompting of custom values
|
||||||
* is fully nestable and composable
|
* is fully nestable and composable
|
||||||
|
|
12
examples/bashcompletion/README
Normal 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
|
41
examples/bashcompletion/bashcompletion.py
Normal 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)
|
15
examples/bashcompletion/setup.py
Normal 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
|
||||||
|
''',
|
||||||
|
)
|
|
@ -2,7 +2,9 @@ import click
|
||||||
|
|
||||||
|
|
||||||
all_colors = 'black', 'red', 'green', 'yellow', 'blue', 'magenta', \
|
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()
|
@click.command()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import click
|
import click
|
||||||
try:
|
try:
|
||||||
from urllib import parser as urlparse
|
from urllib import parse as urlparse
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
|
12
setup.cfg
|
@ -1,8 +1,8 @@
|
||||||
[wheel]
|
[bdist_wheel]
|
||||||
universal = 1
|
universal=1
|
||||||
|
|
||||||
[egg_info]
|
[metadata]
|
||||||
tag_build =
|
license_file = LICENSE
|
||||||
tag_date = 0
|
|
||||||
tag_svn_revision = 0
|
|
||||||
|
|
||||||
|
[tool:pytest]
|
||||||
|
addopts = -p no:warnings --tb=short
|
||||||
|
|
41
setup.py
|
@ -1,28 +1,57 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import io
|
||||||
import re
|
import re
|
||||||
import ast
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
||||||
|
|
||||||
|
|
||||||
with open('click/__init__.py', 'rb') as f:
|
with io.open('README.rst', 'rt', encoding='utf8') as f:
|
||||||
version = str(ast.literal_eval(_version_re.search(
|
readme = f.read()
|
||||||
f.read().decode('utf-8')).group(1)))
|
|
||||||
|
|
||||||
|
with io.open('click/__init__.py', 'rt', encoding='utf8') as f:
|
||||||
|
version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='click',
|
name='click',
|
||||||
|
version=version,
|
||||||
|
url='https://palletsprojects.com/p/click/',
|
||||||
author='Armin Ronacher',
|
author='Armin Ronacher',
|
||||||
author_email='armin.ronacher@active-4.com',
|
author_email='armin.ronacher@active-4.com',
|
||||||
version=version,
|
maintainer='Pallets team',
|
||||||
url='http://github.com/mitsuhiko/click',
|
maintainer_email='contact@palletsprojects.com',
|
||||||
|
long_description=readme,
|
||||||
packages=['click'],
|
packages=['click'],
|
||||||
description='A simple wrapper around optparse for '
|
description='A simple wrapper around optparse for '
|
||||||
'powerful command line utilities.',
|
'powerful command line utilities.',
|
||||||
|
license='BSD',
|
||||||
|
extras_require={
|
||||||
|
'dev': [
|
||||||
|
'pytest>=3',
|
||||||
|
'coverage',
|
||||||
|
'tox',
|
||||||
|
'sphinx',
|
||||||
|
],
|
||||||
|
'docs': [
|
||||||
|
'sphinx',
|
||||||
|
'Pallets-Sphinx-Themes',
|
||||||
|
]
|
||||||
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Python :: 3',
|
'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.*",
|
||||||
)
|
)
|
||||||
|
|
|
@ -188,7 +188,7 @@ def test_empty_nargs(runner):
|
||||||
|
|
||||||
result = runner.invoke(cmd2, [])
|
result = runner.invoke(cmd2, [])
|
||||||
assert result.exit_code == 2
|
assert result.exit_code == 2
|
||||||
assert 'Missing argument "arg"' in result.output
|
assert 'Missing argument "ARG..."' in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_missing_arg(runner):
|
def test_missing_arg(runner):
|
||||||
|
@ -199,7 +199,7 @@ def test_missing_arg(runner):
|
||||||
|
|
||||||
result = runner.invoke(cmd, [])
|
result = runner.invoke(cmd, [])
|
||||||
assert result.exit_code == 2
|
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):
|
def test_implicit_non_required(runner):
|
||||||
|
|
|
@ -4,14 +4,59 @@ import click
|
||||||
from click._bashcomplete import get_choices
|
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():
|
def test_single_command():
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--local-opt')
|
@click.option('--local-opt')
|
||||||
def cli(local_opt):
|
def cli(local_opt):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert list(get_choices(cli, 'lol', [], '-')) == ['--local-opt']
|
assert choices_without_help(cli, [], '-') == ['--local-opt']
|
||||||
assert list(get_choices(cli, 'lol', [], '')) == []
|
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():
|
def test_small_chain():
|
||||||
|
@ -25,10 +70,10 @@ def test_small_chain():
|
||||||
def sub(local_opt):
|
def sub(local_opt):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert list(get_choices(cli, 'lol', [], '')) == ['sub']
|
assert choices_without_help(cli, [], '') == ['sub']
|
||||||
assert list(get_choices(cli, 'lol', [], '-')) == ['--global-opt']
|
assert choices_without_help(cli, [], '-') == ['--global-opt']
|
||||||
assert list(get_choices(cli, 'lol', ['sub'], '')) == []
|
assert choices_without_help(cli, ['sub'], '') == []
|
||||||
assert list(get_choices(cli, 'lol', ['sub'], '-')) == ['--local-opt']
|
assert choices_without_help(cli, ['sub'], '-') == ['--local-opt']
|
||||||
|
|
||||||
|
|
||||||
def test_long_chain():
|
def test_long_chain():
|
||||||
|
@ -47,16 +92,206 @@ def test_long_chain():
|
||||||
def bsub(bsub_opt):
|
def bsub(bsub_opt):
|
||||||
pass
|
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')
|
@bsub.command('csub')
|
||||||
@click.option('--csub-opt')
|
@click.option('--csub-opt', type=click.Choice(CSUB_OPT_CHOICES))
|
||||||
def csub(csub_opt):
|
@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
|
pass
|
||||||
|
|
||||||
assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt']
|
assert choices_without_help(cli, [], '-') == ['--cli-opt']
|
||||||
assert list(get_choices(cli, 'lol', [], '')) == ['asub']
|
assert choices_without_help(cli, [], '') == ['asub']
|
||||||
assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt']
|
assert choices_without_help(cli, ['asub'], '-') == ['--asub-opt']
|
||||||
assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub']
|
assert choices_without_help(cli, ['asub'], '') == ['bsub']
|
||||||
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt']
|
assert choices_without_help(cli, ['asub', 'bsub'], '-') == ['--bsub-opt']
|
||||||
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub']
|
assert choices_without_help(cli, ['asub', 'bsub'], '') == ['csub']
|
||||||
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt']
|
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '-') == ['--csub-opt', '--csub', '--search-color']
|
||||||
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == []
|
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']
|
||||||
|
|
|
@ -180,6 +180,28 @@ def test_boolean_option(runner):
|
||||||
assert result.output == '%s\n' % (default)
|
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):
|
def test_file_option(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--file', type=click.File('w'))
|
@click.option('--file', type=click.File('w'))
|
||||||
|
@ -343,6 +365,39 @@ def test_int_range_option(runner):
|
||||||
assert result.output == '0\n'
|
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):
|
def test_required_option(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--foo', required=True)
|
@click.option('--foo', required=True)
|
||||||
|
@ -357,7 +412,7 @@ def test_required_option(runner):
|
||||||
def test_evaluation_order(runner):
|
def test_evaluation_order(runner):
|
||||||
called = []
|
called = []
|
||||||
|
|
||||||
def memo(ctx, value):
|
def memo(ctx, param, value):
|
||||||
called.append(value)
|
called.append(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -397,3 +452,47 @@ def test_evaluation_order(runner):
|
||||||
'normal1',
|
'normal1',
|
||||||
'missing',
|
'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
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def test_other_command_invoke(runner):
|
def test_other_command_invoke(runner):
|
||||||
|
@ -60,9 +62,9 @@ def test_auto_shorthelp(runner):
|
||||||
result = runner.invoke(cli, ['--help'])
|
result = runner.invoke(cli, ['--help'])
|
||||||
assert re.search(
|
assert re.search(
|
||||||
r'Commands:\n\s+'
|
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'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
|
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.option('--foo', type=click.INT, default=42)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def other_cmd(ctx, foo):
|
def other_cmd(ctx, foo):
|
||||||
assert ctx.info_name == 'other_cmd'
|
assert ctx.info_name == 'other-cmd'
|
||||||
click.echo(foo)
|
click.echo(foo)
|
||||||
|
|
||||||
result = runner.invoke(cli, [])
|
result = runner.invoke(cli, [])
|
||||||
|
@ -253,3 +255,29 @@ def test_unprocessed_options(runner):
|
||||||
'Verbosity: 4',
|
'Verbosity: 4',
|
||||||
'Args: -foo|-x|--muhaha|x|y|-x',
|
'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
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import click
|
import click
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
if click.__version__ >= '3.0':
|
if click.__version__ >= '3.0':
|
||||||
|
@ -11,14 +12,14 @@ if click.__version__ >= '3.0':
|
||||||
def cli(foo):
|
def cli(foo):
|
||||||
click.echo(foo)
|
click.echo(foo)
|
||||||
|
|
||||||
result = runner.invoke(cli, ['--foo', 'wat'])
|
with pytest.warns(Warning, match='Invoked legacy parameter callback'):
|
||||||
assert result.exit_code == 0
|
result = runner.invoke(cli, ['--foo', 'wat'])
|
||||||
assert 'WAT' in result.output
|
assert result.exit_code == 0
|
||||||
assert 'Invoked legacy parameter callback' in result.output
|
assert 'WAT' in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_bash_func_name():
|
def test_bash_func_name():
|
||||||
from click._bashcomplete import get_completion_script
|
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 script.startswith('_foo_barbaz_blah_completion()')
|
||||||
assert '_COMPLETE_VAR=complete $1' in script
|
assert '_COMPLETE_VAR=complete $1' in script
|
||||||
|
|
|
@ -193,6 +193,7 @@ def test_close_before_pop(runner):
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx):
|
def cli(ctx):
|
||||||
ctx.obj = 'test'
|
ctx.obj = 'test'
|
||||||
|
|
||||||
@ctx.call_on_close
|
@ctx.call_on_close
|
||||||
def foo():
|
def foo():
|
||||||
assert click.get_current_context().obj == 'test'
|
assert click.get_current_context().obj == 'test'
|
||||||
|
@ -203,3 +204,55 @@ def test_close_before_pop(runner):
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert result.output == 'aha!\n'
|
assert result.output == 'aha!\n'
|
||||||
assert called == [True]
|
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
|
||||||
|
|
|
@ -74,11 +74,11 @@ def test_wrapping_long_options_strings(runner):
|
||||||
|
|
||||||
# 54 is chosen as a length where the second line is one character
|
# 54 is chosen as a length where the second line is one character
|
||||||
# longer than the maximum length.
|
# 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)
|
terminal_width=54)
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert result.output.splitlines() == [
|
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',
|
' THIRD FOURTH FIFTH',
|
||||||
' SIXTH',
|
' SIXTH',
|
||||||
'',
|
'',
|
||||||
|
@ -111,11 +111,11 @@ def test_wrapping_long_command_name(runner):
|
||||||
"""A command.
|
"""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)
|
terminal_width=54)
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert result.output.splitlines() == [
|
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',
|
' [OPTIONS] FIRST SECOND THIRD FOURTH FIFTH',
|
||||||
' SIXTH',
|
' SIXTH',
|
||||||
'',
|
'',
|
||||||
|
@ -145,3 +145,155 @@ def test_formatting_empty_help_lines(runner):
|
||||||
'Options:',
|
'Options:',
|
||||||
' --help Show this message and exit.',
|
' --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.',
|
||||||
|
]
|
||||||
|
|
|
@ -32,7 +32,7 @@ click.echo(json.dumps(rv))
|
||||||
ALLOWED_IMPORTS = set([
|
ALLOWED_IMPORTS = set([
|
||||||
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
||||||
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
|
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
|
||||||
'threading', 'colorama', 'errno'
|
'threading', 'colorama', 'errno', 'fcntl'
|
||||||
])
|
])
|
||||||
|
|
||||||
if WIN:
|
if WIN:
|
||||||
|
@ -48,7 +48,6 @@ def test_light_imports():
|
||||||
if sys.version_info[0] != 2:
|
if sys.version_info[0] != 2:
|
||||||
rv = rv.decode('utf-8')
|
rv = rv.decode('utf-8')
|
||||||
imported = json.loads(rv)
|
imported = json.loads(rv)
|
||||||
print(imported)
|
|
||||||
|
|
||||||
for module in imported:
|
for module in imported:
|
||||||
if module == 'click' or module.startswith('click.'):
|
if module == 'click' or module.startswith('click.'):
|
||||||
|
|
|
@ -84,7 +84,7 @@ def test_counting(runner):
|
||||||
assert result.output == 'verbosity=0\n'
|
assert result.output == 'verbosity=0\n'
|
||||||
|
|
||||||
result = runner.invoke(cli, ['--help'])
|
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'])
|
@pytest.mark.parametrize('unknown_flag', ['--foo', '-f'])
|
||||||
|
@ -174,6 +174,33 @@ def test_multiple_default_type(runner):
|
||||||
'two --arg2 4 four'.split())
|
'two --arg2 4 four'.split())
|
||||||
assert not result.exception
|
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):
|
def test_nargs_envvar(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
|
@ -198,8 +225,32 @@ def test_nargs_envvar(runner):
|
||||||
assert result.output == 'x|1\ny|2\n'
|
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 test_custom_validation(runner):
|
||||||
def validate_pos_int(ctx, value):
|
def validate_pos_int(ctx, param, value):
|
||||||
if value < 0:
|
if value < 0:
|
||||||
raise click.BadParameter('Value needs to be positive')
|
raise click.BadParameter('Value needs to be positive')
|
||||||
return value
|
return value
|
||||||
|
@ -255,8 +306,42 @@ def test_missing_choice(runner):
|
||||||
|
|
||||||
result = runner.invoke(cmd)
|
result = runner.invoke(cmd)
|
||||||
assert result.exit_code == 2
|
assert result.exit_code == 2
|
||||||
assert 'Error: Missing option "--foo". Choose from foo, bar.' \
|
error, separator, choices = result.output.partition('Choose from')
|
||||||
in result.output
|
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):
|
def test_multiline_help(runner):
|
||||||
|
@ -278,7 +363,6 @@ def test_multiline_help(runner):
|
||||||
assert ' i am' in out
|
assert ' i am' in out
|
||||||
assert ' multiline' in out
|
assert ' multiline' in out
|
||||||
|
|
||||||
|
|
||||||
def test_argument_custom_class(runner):
|
def test_argument_custom_class(runner):
|
||||||
class CustomArgument(click.Argument):
|
class CustomArgument(click.Argument):
|
||||||
def get_default(self, ctx):
|
def get_default(self, ctx):
|
||||||
|
@ -311,6 +395,35 @@ def test_option_custom_class(runner):
|
||||||
assert 'you wont see me' not in result.output
|
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):
|
def test_aliases_for_flags(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--warnings/--no-warnings', ' /-W', default=True)
|
@click.option('--warnings/--no-warnings', ' /-W', default=True)
|
||||||
|
@ -335,3 +448,29 @@ def test_aliases_for_flags(runner):
|
||||||
assert result.output == 'False\n'
|
assert result.output == 'False\n'
|
||||||
result = runner.invoke(cli_alt, ['-w'])
|
result = runner.invoke(cli_alt, ['-w'])
|
||||||
assert result.output == 'True\n'
|
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'
|
||||||
|
|
|
@ -1,14 +1,130 @@
|
||||||
import click
|
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):
|
def test_progressbar_strip_regression(runner, monkeypatch):
|
||||||
|
fake_clock = FakeClock()
|
||||||
label = ' padded line'
|
label = ' padded line'
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def cli():
|
def cli():
|
||||||
with click.progressbar(tuple(range(10)), label=label) as progress:
|
with click.progressbar(tuple(range(10)), label=label) as progress:
|
||||||
for thing in 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)
|
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
|
||||||
assert label in runner.invoke(cli, []).output
|
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]
|
||||||
|
|
|
@ -153,16 +153,34 @@ def test_exit_code_and_output_from_sys_exit():
|
||||||
click.echo('hello world')
|
click.echo('hello world')
|
||||||
sys.exit('error')
|
sys.exit('error')
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def cli_string_ctx_exit(ctx):
|
||||||
|
click.echo('hello world')
|
||||||
|
ctx.exit('error')
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def cli_int():
|
def cli_int():
|
||||||
click.echo('hello world')
|
click.echo('hello world')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def cli_int_ctx_exit(ctx):
|
||||||
|
click.echo('hello world')
|
||||||
|
ctx.exit(1)
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def cli_float():
|
def cli_float():
|
||||||
click.echo('hello world')
|
click.echo('hello world')
|
||||||
sys.exit(1.0)
|
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()
|
@click.command()
|
||||||
def cli_no_error():
|
def cli_no_error():
|
||||||
click.echo('hello world')
|
click.echo('hello world')
|
||||||
|
@ -173,14 +191,26 @@ def test_exit_code_and_output_from_sys_exit():
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
assert result.output == 'hello world\nerror\n'
|
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)
|
result = runner.invoke(cli_int)
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
assert result.output == 'hello world\n'
|
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)
|
result = runner.invoke(cli_float)
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
assert result.output == 'hello world\n1.0\n'
|
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)
|
result = runner.invoke(cli_no_error)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == 'hello world\n'
|
assert result.output == 'hello world\n'
|
||||||
|
@ -202,3 +232,58 @@ def test_env():
|
||||||
assert result.output == 'ENV=some_value\n'
|
assert result.output == 'ENV=some_value\n'
|
||||||
|
|
||||||
assert os.environ == env_orig
|
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'
|
||||||
|
|
|
@ -10,13 +10,13 @@ from click._compat import WIN, PY2
|
||||||
|
|
||||||
|
|
||||||
def test_echo(runner):
|
def test_echo(runner):
|
||||||
with runner.isolation() as out:
|
with runner.isolation() as outstreams:
|
||||||
click.echo(u'\N{SNOWMAN}')
|
click.echo(u'\N{SNOWMAN}')
|
||||||
click.echo(b'\x44\x44')
|
click.echo(b'\x44\x44')
|
||||||
click.echo(42, nl=False)
|
click.echo(42, nl=False)
|
||||||
click.echo(b'a', nl=False)
|
click.echo(b'a', nl=False)
|
||||||
click.echo('\x1b[31mx\x1b[39m', 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'
|
assert bytes == b'\xe2\x98\x83\nDD\n42ax'
|
||||||
|
|
||||||
# If we are in Python 2, we expect that writing bytes into a string io
|
# 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():
|
def cli():
|
||||||
click.echo(b'\xf6')
|
click.echo(b'\xf6')
|
||||||
result = runner.invoke(cli, [])
|
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.
|
# 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)
|
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():
|
def test_echo_custom_file():
|
||||||
|
@ -146,14 +146,36 @@ def test_prompts_abort(monkeypatch, capsys):
|
||||||
assert out == 'Password: \nScrew you.\n'
|
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.skipif(WIN, reason='Different behavior on windows.')
|
||||||
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
|
@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.setitem(os.environ, 'PAGER', cat)
|
||||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda x: True)
|
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()
|
out, err = capfd.readouterr()
|
||||||
assert out == 'haha\n'
|
assert out == expected_output
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
@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)))
|
expected = list(map(str, range(10)))
|
||||||
p = tmpdir.mkdir('testdir').join('testfile')
|
p = tmpdir.mkdir('testdir').join('testfile')
|
||||||
p.write(os.linesep.join(expected))
|
p.write(os.linesep.join(expected))
|
||||||
f = p.open()
|
with p.open() as f:
|
||||||
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
|
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
|
||||||
assert e_line == a_line.strip()
|
assert e_line == a_line.strip()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
@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)))
|
expected = list(map(str, range(10)))
|
||||||
p = tmpdir.mkdir('testdir').join('testfile')
|
p = tmpdir.mkdir('testdir').join('testfile')
|
||||||
p.write(os.linesep.join(expected))
|
p.write(os.linesep.join(expected))
|
||||||
f = p.open()
|
with p.open() as f:
|
||||||
for e_line, a_line in zip(expected, click.utils.LazyFile(f.name)):
|
with click.utils.LazyFile(f.name) as lf:
|
||||||
assert e_line == a_line.strip()
|
for e_line, a_line in zip(expected, lf):
|
||||||
|
assert e_line == a_line.strip()
|
||||||
|
|
35
tox.ini
Normal 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
|