Imported Upstream version 3.3
184
CHANGES
Normal file
|
@ -0,0 +1,184 @@
|
|||
Click Changelog
|
||||
===============
|
||||
|
||||
This contains all major version changes between Click releases.
|
||||
|
||||
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.
|
38
LICENSE
Normal file
|
@ -0,0 +1,38 @@
|
|||
Copyright (c) 2014 by Armin Ronacher.
|
||||
|
||||
Click uses parts of optparse written by Gregory P. Ward and maintained by the
|
||||
Python software foundation. This is limited to code in the parser.py
|
||||
module:
|
||||
|
||||
Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved.
|
||||
Copyright (c) 2002-2006 Python Software Foundation. All rights reserved.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
12
MANIFEST.in
Normal file
|
@ -0,0 +1,12 @@
|
|||
include Makefile CHANGES LICENSE
|
||||
recursive-include artwork *
|
||||
recursive-include tests *
|
||||
recursive-include examples *
|
||||
recursive-include docs *
|
||||
recursive-exclude docs *.pyc
|
||||
recursive-exclude docs *.pyo
|
||||
recursive-exclude tests *.pyc
|
||||
recursive-exclude tests *.pyo
|
||||
recursive-exclude examples *.pyc
|
||||
recursive-exclude examples *.pyo
|
||||
prune docs/_build
|
8
Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
test:
|
||||
@cd tests; PYTHONPATH=.. py.test --tb=short
|
||||
|
||||
upload-docs:
|
||||
$(MAKE) -C docs dirhtml
|
||||
rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/click.pocoo.org/static/
|
||||
|
||||
.PHONY: upload-docs
|
13
PKG-INFO
Normal file
|
@ -0,0 +1,13 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: click
|
||||
Version: 3.3
|
||||
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
Normal file
|
@ -0,0 +1,20 @@
|
|||
$ 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 a work in progress. Please give feedback!
|
75
artwork/logo.svg
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="237.36929"
|
||||
height="110.7928"
|
||||
id="svg4837"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.2 r9819"
|
||||
sodipodi:docname="New document 8">
|
||||
<defs
|
||||
id="defs4839" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="259.76814"
|
||||
inkscape:cy="40.769955"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="10"
|
||||
fit-margin-left="10"
|
||||
fit-margin-right="10"
|
||||
fit-margin-bottom="10"
|
||||
inkscape:window-width="1676"
|
||||
inkscape:window-height="1006"
|
||||
inkscape:window-x="4"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata4842">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-255.43458,-207.38101)">
|
||||
<path
|
||||
style="fill:#000000"
|
||||
d="m 466.33424,306.48462 0,-1.6892 3.16724,0 3.16723,0 0,1.6892 0,1.68919 -3.16723,0 -3.16724,0 0,-1.68919 z m -3.37839,-5.06759 0,-3.37839 1.47804,0 1.47805,0 0,3.37839 0,3.37839 -1.47805,0 -1.47804,0 0,-3.37839 z m 10.13516,0 0,-3.37839 1.47804,0 1.47805,0 0,3.37839 0,3.37839 -1.47805,0 -1.47804,0 0,-3.37839 z m -30.82778,-28.92744 0,-28.92744 1.68919,0 c 1.68919,0 1.68919,0 1.68919,1.68919 0,1.6892 0,1.6892 1.6892,1.6892 1.68919,0 1.68919,0 1.68919,1.68919 0,1.6892 0,1.6892 1.6892,1.6892 1.68919,0 1.68919,0 1.68919,1.68919 0,1.6892 0,1.6892 -1.68919,1.6892 -1.6892,0 -1.6892,0 -1.6892,-1.6892 0,-1.68919 0,-1.68919 -1.68919,-1.68919 l -1.6892,0 0,23.85986 0,23.85985 1.6892,0 c 1.68919,0 1.68919,0 1.68919,1.6892 l 0,1.68919 -3.37839,0 -3.37838,0 0,-28.92744 z m 7.00029,25.22951 c -0.13394,-0.13393 -0.24352,-0.87696 -0.24352,-1.65118 0,-1.36423 0.0529,-1.40766 1.71469,-1.40766 l 1.71468,0 -0.13106,1.58362 c -0.12024,1.45279 -0.24178,1.5892 -1.47117,1.65118 -0.73706,0.0372 -1.44968,-0.042 -1.58362,-0.17596 z m 10.14204,-0.0392 c -0.13814,-0.22352 -0.20454,-1.7545 -0.14756,-3.40219 l 0.10361,-2.99581 1.60373,0 1.60374,0 -0.12334,3.27282 -0.12334,3.27281 -1.33284,0.12938 c -0.73305,0.0712 -1.44586,-0.0535 -1.584,-0.27702 z m 10.30707,-3.03247 0,-3.36553 1.47803,0 1.47805,0 0,3.30997 0,3.30996 -1.47805,0.0556 -1.47803,0.0556 0,-3.36553 z m -17.03271,-0.26872 c -0.15484,-0.15483 -0.28153,-0.91497 -0.28153,-1.68919 0,-1.36073 0.0563,-1.40766 1.68919,-1.40766 1.6892,0 1.6892,0 1.6892,1.6892 0,1.63289 -0.0469,1.68919 -1.40766,1.68919 -0.77422,0 -1.53436,-0.12669 -1.6892,-0.28154 z m 3.09686,-4.99719 c 0,-1.44286 0.0402,-1.47804 1.68919,-1.47804 1.64898,0 1.68919,0.0352 1.68919,1.47804 0,1.44285 -0.0402,1.47805 -1.68919,1.47805 -1.64898,0 -1.68919,-0.0352 -1.68919,-1.47805 z m 10.55746,-1.68919 0,-3.16724 6.75677,0 6.75678,0 0,-1.68919 c 0,-1.64898 0.0351,-1.6892 1.47804,-1.6892 l 1.47804,0 0,3.15652 0,3.15651 -6.6512,0.1163 -6.65119,0.1163 -0.13108,1.58361 c -0.12394,1.49763 -0.20994,1.58363 -1.58361,1.58363 l -1.45255,0 0,-3.16724 z m 10.13516,-8.44597 c 0,-1.40766 0.0704,-1.47804 1.47804,-1.47804 1.40766,0 1.47805,0.0704 1.47805,1.47804 0,1.40766 -0.0704,1.47804 -1.47805,1.47804 -1.40766,0 -1.47804,-0.0704 -1.47804,-1.47804 z m -3.37839,-3.37839 c 0,-1.40766 0.0704,-1.47804 1.47804,-1.47804 1.40767,0 1.47805,0.0704 1.47805,1.47804 0,1.40767 -0.0704,1.47805 -1.47805,1.47805 -1.40765,0 -1.47804,-0.0704 -1.47804,-1.47805 z m -3.37838,-3.37839 c 0,-1.40765 0.0704,-1.47804 1.47803,-1.47804 1.40767,0 1.47805,0.0704 1.47805,1.47804 0,1.40767 -0.0704,1.47805 -1.47805,1.47805 -1.40765,0 -1.47803,-0.0704 -1.47803,-1.47805 z m -3.37839,-3.48871 c 0,-1.52897 0.0553,-1.58931 1.47804,-1.61346 1.43567,-0.0244 1.47804,0.0211 1.47804,1.58838 0,1.56241 -0.0467,1.61345 -1.47804,1.61345 -1.42721,0 -1.47804,-0.0546 -1.47804,-1.58837 z m -3.37839,-3.47921 c 0,-1.64897 0.0351,-1.68919 1.47804,-1.68919 1.44285,0 1.47805,0.0402 1.47805,1.68919 0,1.64898 -0.0352,1.6892 -1.47805,1.6892 -1.44285,0 -1.47804,-0.0403 -1.47804,-1.6892 z m -3.45076,-3.37839 c 0.15328,-1.60738 0.23052,-1.68919 1.59478,-1.68919 1.3911,0 1.43368,0.0502 1.43368,1.68919 0,1.67253 -0.0157,1.6892 -1.59478,1.6892 l -1.59477,0 0.16109,-1.6892 z m -3.30601,-3.37838 c 0,-1.64898 0.0351,-1.6892 1.47804,-1.6892 1.44285,0 1.47804,0.0402 1.47804,1.6892 0,1.64897 -0.0352,1.68919 -1.47804,1.68919 -1.44286,0 -1.47804,-0.0402 -1.47804,-1.68919 z m -3.72061,-3.27282 c 0.12395,-1.49762 0.21706,-1.59073 1.71468,-1.71468 l 1.58363,-0.13107 0,1.71469 0,1.71468 -1.71469,0 -1.71468,0 0.13106,-1.58362 z"
|
||||
id="path4856"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#000000"
|
||||
d="m 271.34527,247.46047 c 0,-1.32826 -0.94153,-2.12734 -3.05297,-2.59109 -4.04056,-0.88746 -3.19986,-3.08429 1.01754,-2.65894 9.32239,0.94022 10.81407,-3.50307 2.60795,-7.76831 -2.71012,-1.40862 -5.36388,-3.27897 -5.89726,-4.15634 -1.47604,-2.42802 -0.0246,-6.42458 2.80076,-7.7119 1.48697,-0.67751 2.52398,-1.98054 2.52398,-3.17144 0,-1.13017 0.66136,-2.02144 1.5,-2.02144 0.825,0 1.5,0.85122 1.5,1.89159 0,1.19765 0.94321,2.12832 2.57104,2.53688 3.91591,0.98283 2.39946,3.10499 -1.95093,2.73019 -6.99788,-0.60291 -8.93527,3.8291 -2.87011,6.5657 9.0905,4.10163 11.015,9.73021 4.5,13.16112 -1.2375,0.65169 -2.25,2.06905 -2.25,3.1497 0,1.08065 -0.675,1.96482 -1.5,1.96482 -0.825,0 -1.5,-0.86425 -1.5,-1.92054 z m 38.357,-3.07104 c -4.57947,-1.84804 -6.77791,-8.48491 -4.32736,-13.06381 2.11183,-3.94599 10.30093,-6.28597 13.9414,-3.98367 2.2687,1.43477 0.36183,2.48777 -3.94232,2.177 -6.96682,-0.50302 -10.61631,6.27447 -5.91184,10.97894 1.71218,1.71218 2.95483,2.02243 6.5,1.62284 3.13626,-0.35351 4.38312,-0.13272 4.38312,0.77613 0,2.4062 -6.21813,3.27822 -10.643,1.49257 z m 21.1997,-1.23093 c -1.16379,-1.66155 -1.5567,-4.81653 -1.5567,-12.5 l 0,-10.27749 -2.5,0 c -1.55556,0 -2.5,-0.56667 -2.5,-1.5 0,-1.08333 1.11111,-1.5 4,-1.5 l 4,0 0.0153,11.25 c 0.008,6.1875 0.41056,11.87411 0.89363,12.63691 0.54214,0.85607 1.88505,1.19446 3.50856,0.88411 2.94677,-0.56331 4.58181,0.98192 2.46061,2.32547 -2.5829,1.63598 -6.70984,0.98184 -8.32141,-1.319 z m 20.4433,0.22251 c -1.55556,-1.55556 -2,-3.33333 -2,-8 0,-5.73333 -0.11111,-6 -2.5,-6 -1.55556,0 -2.5,-0.56667 -2.5,-1.5 0,-1.08333 1.11111,-1.5 4,-1.5 l 4,0 0.0153,6.75 c 0.0183,8.04891 0.82623,9.70461 4.40219,9.02102 2.94677,-0.56331 4.58181,0.98192 2.46061,2.32547 -2.3358,1.47948 -5.78176,0.99986 -7.87811,-1.09649 z m 18.357,1.00842 c -4.57947,-1.84804 -6.77791,-8.48491 -4.32736,-13.06381 2.11183,-3.94599 10.30093,-6.28597 13.9414,-3.98367 2.2687,1.43477 0.36183,2.48777 -3.94232,2.177 -6.96682,-0.50302 -10.61631,6.27447 -5.91184,10.97894 1.71218,1.71218 2.95483,2.02243 6.5,1.62284 3.13626,-0.35351 4.38312,-0.13272 4.38312,0.77613 0,2.4062 -6.21813,3.27822 -10.643,1.49257 z m 15.86478,-12.67646 c 0.20032,-9.84119 0.6282,-13.78431 1.52822,-14.08333 0.9358,-0.31091 1.25628,1.80502 1.275,8.41804 l 0.025,8.83333 4.01111,-4.25 c 2.32594,-2.46446 4.92367,-4.25 6.18321,-4.25 1.8968,0 1.6121,0.57059 -2.24643,4.50214 l -4.41851,4.50213 4.92271,4.99787 c 3.84161,3.90023 4.49929,4.99786 2.99467,4.99786 -1.06044,0 -4.08102,-2.12058 -6.71241,-4.71241 l -4.78435,-4.71241 0,4.71241 c 0,3.56082 -0.37346,4.71241 -1.52822,4.71241 -1.30999,0 -1.48849,-1.95179 -1.25,-13.66804 z m -37.85759,-9.82527 c -0.34636,-0.90262 -0.15025,-2.12063 0.43581,-2.70669 1.52361,-1.52361 4.41041,-0.13109 4.01242,1.93549 -0.4433,2.30188 -3.64766,2.85743 -4.44823,0.7712 z"
|
||||
id="path4887"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||
d="m 411.32164,243.39982 c 7.38153,0.15924 14.76525,0.0821 22.14736,0.0136 -3.6814,3.83663 -9.42739,1.45689 -14.06576,2.07921 -3.69684,-0.007 -7.3939,0.0151 -11.09,0.0923 1.0029,-0.72825 2.00505,-1.45754 3.0084,-2.18514 z"
|
||||
id="path4889"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.5 KiB |
13
click.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,13 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: click
|
||||
Version: 3.3
|
||||
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
|
122
click.egg-info/SOURCES.txt
Normal file
|
@ -0,0 +1,122 @@
|
|||
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/core.py
|
||||
click/decorators.py
|
||||
click/exceptions.py
|
||||
click/formatting.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/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/_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
|
||||
docs/_themes/LICENSE
|
||||
docs/_themes/README
|
||||
docs/_themes/click/layout.html
|
||||
docs/_themes/click/relations.html
|
||||
docs/_themes/click/theme.conf
|
||||
docs/_themes/click/static/click.css_t
|
||||
examples/.DS_Store
|
||||
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/.DS_Store
|
||||
examples/imagepipe/.gitignore
|
||||
examples/imagepipe/README
|
||||
examples/imagepipe/example01.jpg
|
||||
examples/imagepipe/example02.jpg
|
||||
examples/imagepipe/imagepipe.py
|
||||
examples/imagepipe/setup.py
|
||||
examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO
|
||||
examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt
|
||||
examples/imagepipe/click_example_imagepipe.egg-info/dependency_links.txt
|
||||
examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt
|
||||
examples/imagepipe/click_example_imagepipe.egg-info/requires.txt
|
||||
examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt
|
||||
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_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
click.egg-info/dependency_links.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
click.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
click
|
85
click/__init__.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
click
|
||||
~~~~~
|
||||
|
||||
Click is a simple Python module that wraps the stdlib's optparse to make
|
||||
writing command line scripts fun. Unlike other modules, it's based around
|
||||
a simple API that does not come with too much magic and is composable.
|
||||
|
||||
In case optparse ever gets removed from the stdlib, it will be shipped by
|
||||
this module.
|
||||
|
||||
:copyright: (c) 2014 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
# Core classes
|
||||
from .core import Context, BaseCommand, Command, MultiCommand, Group, \
|
||||
CommandCollection, Parameter, Option, Argument
|
||||
|
||||
# Decorators
|
||||
from .decorators import pass_context, pass_obj, make_pass_decorator, \
|
||||
command, group, argument, option, confirmation_option, \
|
||||
password_option, version_option, help_option
|
||||
|
||||
# Types
|
||||
from .types import ParamType, File, Path, Choice, IntRange, STRING, INT, \
|
||||
FLOAT, BOOL, UUID
|
||||
|
||||
# Utilities
|
||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
||||
format_filename, get_app_dir
|
||||
|
||||
# Terminal functions
|
||||
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
||||
progressbar, clear, style, unstyle, secho, edit, launch, getchar, \
|
||||
pause
|
||||
|
||||
# Exceptions
|
||||
from .exceptions import ClickException, UsageError, BadParameter, \
|
||||
FileError, Abort
|
||||
|
||||
# Formatting
|
||||
from .formatting import HelpFormatter, wrap_text
|
||||
|
||||
# Parsing
|
||||
from .parser import OptionParser
|
||||
|
||||
|
||||
__all__ = [
|
||||
# Core classes
|
||||
'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
|
||||
'CommandCollection', 'Parameter', 'Option', 'Argument',
|
||||
|
||||
# Decorators
|
||||
'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
|
||||
'argument', 'option', 'confirmation_option', 'password_option',
|
||||
'version_option', 'help_option',
|
||||
|
||||
# Types
|
||||
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'STRING', 'INT',
|
||||
'FLOAT', 'BOOL', 'UUID',
|
||||
|
||||
# Utilities
|
||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||
'format_filename', 'get_app_dir',
|
||||
|
||||
# Terminal functions
|
||||
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
||||
'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch',
|
||||
'getchar', 'pause',
|
||||
|
||||
# Exceptions
|
||||
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
||||
'Abort',
|
||||
|
||||
# Formatting
|
||||
'HelpFormatter', 'wrap_text',
|
||||
|
||||
# Parsing
|
||||
'OptionParser',
|
||||
]
|
||||
|
||||
|
||||
__version__ = '3.3'
|
74
click/_bashcomplete.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
import os
|
||||
from .utils import echo
|
||||
from .parser import split_arg_string
|
||||
from .core import MultiCommand, Option
|
||||
|
||||
|
||||
COMPLETION_SCRIPT = '''
|
||||
%(complete_func)s() {
|
||||
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
%(autocomplete_var)s=complete $1 ) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F %(complete_func)s -o default %(script_names)s
|
||||
'''
|
||||
|
||||
|
||||
def get_completion_script(prog_name, complete_var):
|
||||
return (COMPLETION_SCRIPT % {
|
||||
'complete_func': '_%s_completion' % prog_name,
|
||||
'script_names': prog_name,
|
||||
'autocomplete_var': complete_var,
|
||||
}).strip() + ';'
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
||||
while ctx.args and isinstance(ctx.command, MultiCommand):
|
||||
cmd = ctx.command.get_command(ctx, ctx.args[0])
|
||||
if cmd is None:
|
||||
return None
|
||||
ctx = cmd.make_context(ctx.args[0], ctx.args[1:], parent=ctx,
|
||||
resilient_parsing=True)
|
||||
return ctx
|
||||
|
||||
|
||||
def do_complete(cli, prog_name):
|
||||
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
args = cwords[1:cword]
|
||||
try:
|
||||
incomplete = cwords[cword]
|
||||
except IndexError:
|
||||
incomplete = ''
|
||||
|
||||
ctx = resolve_ctx(cli, prog_name, args)
|
||||
if ctx is None:
|
||||
return True
|
||||
|
||||
choices = []
|
||||
if incomplete and not incomplete[:1].isalnum():
|
||||
for param in ctx.command.params:
|
||||
if not isinstance(param, Option):
|
||||
continue
|
||||
choices.extend(param.opts)
|
||||
choices.extend(param.secondary_opts)
|
||||
elif isinstance(ctx.command, MultiCommand):
|
||||
choices.extend(ctx.command.list_commands(ctx))
|
||||
|
||||
for item in choices:
|
||||
if item.startswith(incomplete):
|
||||
echo(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||
if complete_instr == 'source':
|
||||
echo(get_completion_script(prog_name, complete_var))
|
||||
return True
|
||||
elif complete_instr == 'complete':
|
||||
return do_complete(cli, prog_name)
|
||||
return False
|
560
click/_compat.py
Normal file
|
@ -0,0 +1,560 @@
|
|||
import re
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith('win')
|
||||
DEFAULT_COLUMNS = 80
|
||||
|
||||
|
||||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
|
||||
def _make_text_stream(stream, encoding, errors):
|
||||
if encoding is None:
|
||||
encoding = get_best_encoding(stream)
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
||||
line_buffering=True)
|
||||
|
||||
|
||||
def is_ascii_encoding(encoding):
|
||||
"""Checks if a given encoding is ascii."""
|
||||
try:
|
||||
return codecs.lookup(encoding).name == 'ascii'
|
||||
except LookupError:
|
||||
return False
|
||||
|
||||
|
||||
def get_best_encoding(stream):
|
||||
"""Returns the default stream encoding if not found."""
|
||||
rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding()
|
||||
if is_ascii_encoding(rv):
|
||||
return 'utf-8'
|
||||
return rv
|
||||
|
||||
|
||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||
|
||||
def __init__(self, stream, encoding, errors, **extra):
|
||||
self._stream = stream = _FixupStream(stream)
|
||||
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
||||
|
||||
# The io module is a place where the Python 3 text behavior
|
||||
# was forced upon Python 2, so we need to unbreak
|
||||
# it to look like Python 2.
|
||||
if PY2:
|
||||
def write(self, x):
|
||||
if isinstance(x, str) or is_bytes(x):
|
||||
try:
|
||||
self.flush()
|
||||
except Exception:
|
||||
pass
|
||||
return self.buffer.write(str(x))
|
||||
return io.TextIOWrapper.write(self, x)
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.detach()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
# https://bitbucket.org/pypy/pypy/issue/1803
|
||||
return self._stream.isatty()
|
||||
|
||||
|
||||
class _FixupStream(object):
|
||||
"""The new io interface needs more from streams than streams
|
||||
traditionally implement. As such, this fix-up code is necessary in
|
||||
some circumstances.
|
||||
"""
|
||||
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._stream, name)
|
||||
|
||||
def read1(self, size):
|
||||
f = getattr(self._stream, 'read1', None)
|
||||
if f is not None:
|
||||
return f(size)
|
||||
# We only dispatch to readline instead of read in Python 2 as we
|
||||
# do not want cause problems with the different implementation
|
||||
# of line buffering.
|
||||
if PY2:
|
||||
return self._stream.readline(size)
|
||||
return self._stream.read(size)
|
||||
|
||||
def readable(self):
|
||||
x = getattr(self._stream, 'readable', None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.read(0)
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
x = getattr(self._stream, 'writable', None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.write('')
|
||||
except Exception:
|
||||
try:
|
||||
self._stream.write(b'')
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def seekable(self):
|
||||
x = getattr(self._stream, 'seekable', None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.seek(self._stream.tell())
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if PY2:
|
||||
text_type = unicode
|
||||
bytes = str
|
||||
raw_input = raw_input
|
||||
string_types = (str, unicode)
|
||||
iteritems = lambda x: x.iteritems()
|
||||
range_type = xrange
|
||||
|
||||
def is_bytes(x):
|
||||
return isinstance(x, (buffer, bytearray))
|
||||
|
||||
_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
|
||||
|
||||
# For Windows, we need to force stdout/stdin/stderr to binary if it's
|
||||
# fetched for that. This obviously is not the most correct way to do
|
||||
# it as it changes global state. Unfortunately, there does not seem to
|
||||
# be a clear better way to do it as just reopening the file in binary
|
||||
# mode does not change anything.
|
||||
#
|
||||
# An option would be to do what Python 3 does and to open the file as
|
||||
# binary only, patch it back to the system, and then use a wrapper
|
||||
# stream that converts newlines. It's not quite clear what's the
|
||||
# correct option here.
|
||||
if WIN:
|
||||
import msvcrt
|
||||
def set_binary_mode(f):
|
||||
try:
|
||||
fileno = f.fileno()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
msvcrt.setmode(fileno, os.O_BINARY)
|
||||
return f
|
||||
else:
|
||||
set_binary_mode = lambda x: x
|
||||
|
||||
def isidentifier(x):
|
||||
return _identifier_re.search(x) is not None
|
||||
|
||||
def get_binary_stdin():
|
||||
return set_binary_mode(sys.stdin)
|
||||
|
||||
def get_binary_stdout():
|
||||
return set_binary_mode(sys.stdout)
|
||||
|
||||
def get_binary_stderr():
|
||||
return set_binary_mode(sys.stderr)
|
||||
|
||||
def get_text_stdin(encoding=None, errors=None):
|
||||
return _make_text_stream(sys.stdin, encoding, errors)
|
||||
|
||||
def get_text_stdout(encoding=None, errors=None):
|
||||
return _make_text_stream(sys.stdout, encoding, errors)
|
||||
|
||||
def get_text_stderr(encoding=None, errors=None):
|
||||
return _make_text_stream(sys.stderr, encoding, errors)
|
||||
|
||||
def filename_to_ui(value):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(sys.getfilesystemencoding(), 'replace')
|
||||
return value
|
||||
else:
|
||||
import io
|
||||
text_type = str
|
||||
raw_input = input
|
||||
string_types = (str,)
|
||||
range_type = range
|
||||
isidentifier = lambda x: x.isidentifier()
|
||||
iteritems = lambda x: iter(x.items())
|
||||
|
||||
def is_bytes(x):
|
||||
return isinstance(x, (bytes, memoryview, bytearray))
|
||||
|
||||
def _is_binary_reader(stream, default=False):
|
||||
try:
|
||||
return isinstance(stream.read(0), bytes)
|
||||
except Exception:
|
||||
return default
|
||||
# This happens in some cases where the stream was already
|
||||
# closed. In this case, we assume the default.
|
||||
|
||||
def _is_binary_writer(stream, default=False):
|
||||
try:
|
||||
stream.write(b'')
|
||||
except Exception:
|
||||
try:
|
||||
stream.write('')
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
return default
|
||||
return True
|
||||
|
||||
def _find_binary_reader(stream):
|
||||
# We need to figure out if the given stream is already binary.
|
||||
# This can happen because the official docs recommend detaching
|
||||
# the streams to get binary streams. Some code might do this, so
|
||||
# we need to deal with this case explicitly.
|
||||
if _is_binary_reader(stream, False):
|
||||
return stream
|
||||
|
||||
buf = getattr(stream, 'buffer', None)
|
||||
|
||||
# Same situation here; this time we assume that the buffer is
|
||||
# actually binary in case it's closed.
|
||||
if buf is not None and _is_binary_reader(buf, True):
|
||||
return buf
|
||||
|
||||
def _find_binary_writer(stream):
|
||||
# We need to figure out if the given stream is already binary.
|
||||
# This can happen because the official docs recommend detatching
|
||||
# the streams to get binary streams. Some code might do this, so
|
||||
# we need to deal with this case explicitly.
|
||||
if _is_binary_writer(stream, False):
|
||||
return stream
|
||||
|
||||
buf = getattr(stream, 'buffer', None)
|
||||
|
||||
# Same situation here; this time we assume that the buffer is
|
||||
# actually binary in case it's closed.
|
||||
if buf is not None and _is_binary_writer(buf, True):
|
||||
return buf
|
||||
|
||||
def _stream_is_misconfigured(stream):
|
||||
"""A stream is misconfigured if its encoding is ASCII."""
|
||||
return is_ascii_encoding(getattr(stream, 'encoding', None))
|
||||
|
||||
def _is_compatible_text_stream(stream, encoding, errors):
|
||||
stream_encoding = getattr(stream, 'encoding', None)
|
||||
stream_errors = getattr(stream, 'errors', None)
|
||||
|
||||
# Perfect match.
|
||||
if stream_encoding == encoding and stream_errors == errors:
|
||||
return True
|
||||
|
||||
# Otherwise, it's only a compatible stream if we did not ask for
|
||||
# an encoding.
|
||||
if encoding is None:
|
||||
return stream_encoding is not None
|
||||
|
||||
return False
|
||||
|
||||
def _force_correct_text_reader(text_reader, encoding, errors):
|
||||
if _is_binary_reader(text_reader, False):
|
||||
binary_reader = text_reader
|
||||
else:
|
||||
# If there is no target encoding set, we need to verify that the
|
||||
# reader is not actually misconfigured.
|
||||
if encoding is None and not _stream_is_misconfigured(text_reader):
|
||||
return text_reader
|
||||
|
||||
if _is_compatible_text_stream(text_reader, encoding, errors):
|
||||
return text_reader
|
||||
|
||||
# If the reader has no encoding, we try to find the underlying
|
||||
# binary reader for it. If that fails because the environment is
|
||||
# misconfigured, we silently go with the same reader because this
|
||||
# is too common to happen. In that case, mojibake is better than
|
||||
# exceptions.
|
||||
binary_reader = _find_binary_reader(text_reader)
|
||||
if binary_reader is None:
|
||||
return text_reader
|
||||
|
||||
# At this point, we default the errors to replace instead of strict
|
||||
# because nobody handles those errors anyways and at this point
|
||||
# we're so fundamentally fucked that nothing can repair it.
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _make_text_stream(binary_reader, encoding, errors)
|
||||
|
||||
def _force_correct_text_writer(text_writer, encoding, errors):
|
||||
if _is_binary_writer(text_writer, False):
|
||||
binary_writer = text_writer
|
||||
else:
|
||||
# If there is no target encoding set, we need to verify that the
|
||||
# writer is not actually misconfigured.
|
||||
if encoding is None and not _stream_is_misconfigured(text_writer):
|
||||
return text_writer
|
||||
|
||||
if _is_compatible_text_stream(text_writer, encoding, errors):
|
||||
return text_writer
|
||||
|
||||
# If the writer has no encoding, we try to find the underlying
|
||||
# binary writer for it. If that fails because the environment is
|
||||
# misconfigured, we silently go with the same writer because this
|
||||
# is too common to happen. In that case, mojibake is better than
|
||||
# exceptions.
|
||||
binary_writer = _find_binary_writer(text_writer)
|
||||
if binary_writer is None:
|
||||
return text_writer
|
||||
|
||||
# At this point, we default the errors to replace instead of strict
|
||||
# because nobody handles those errors anyways and at this point
|
||||
# we're so fundamentally fucked that nothing can repair it.
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _make_text_stream(binary_writer, encoding, errors)
|
||||
|
||||
def get_binary_stdin():
|
||||
reader = _find_binary_reader(sys.stdin)
|
||||
if reader is None:
|
||||
raise RuntimeError('Was not able to determine binary '
|
||||
'stream for sys.stdin.')
|
||||
return reader
|
||||
|
||||
def get_binary_stdout():
|
||||
writer = _find_binary_writer(sys.stdout)
|
||||
if writer is None:
|
||||
raise RuntimeError('Was not able to determine binary '
|
||||
'stream for sys.stdout.')
|
||||
return writer
|
||||
|
||||
def get_binary_stderr():
|
||||
writer = _find_binary_writer(sys.stderr)
|
||||
if writer is None:
|
||||
raise RuntimeError('Was not able to determine binary '
|
||||
'stream for sys.stderr.')
|
||||
return writer
|
||||
|
||||
def get_text_stdin(encoding=None, errors=None):
|
||||
return _force_correct_text_reader(sys.stdin, encoding, errors)
|
||||
|
||||
def get_text_stdout(encoding=None, errors=None):
|
||||
return _force_correct_text_writer(sys.stdout, encoding, errors)
|
||||
|
||||
def get_text_stderr(encoding=None, errors=None):
|
||||
return _force_correct_text_writer(sys.stderr, encoding, errors)
|
||||
|
||||
def filename_to_ui(value):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(sys.getfilesystemencoding(), 'replace')
|
||||
else:
|
||||
value = value.encode('utf-8', 'surrogateescape') \
|
||||
.decode('utf-8', 'replace')
|
||||
return value
|
||||
|
||||
|
||||
def get_streerror(e, default=None):
|
||||
if hasattr(e, 'strerror'):
|
||||
msg = e.strerror
|
||||
else:
|
||||
if default is not None:
|
||||
msg = default
|
||||
else:
|
||||
msg = str(e)
|
||||
if isinstance(msg, bytes):
|
||||
msg = msg.decode('utf-8', 'replace')
|
||||
return msg
|
||||
|
||||
|
||||
def open_stream(filename, mode='r', encoding=None, errors='strict',
|
||||
atomic=False):
|
||||
# Standard streams first. These are simple because they don't need
|
||||
# special handling for the atomic flag. It's entirely ignored.
|
||||
if filename == '-':
|
||||
if 'w' in mode:
|
||||
if 'b' in mode:
|
||||
return get_binary_stdout(), False
|
||||
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||
if 'b' in mode:
|
||||
return get_binary_stdin(), False
|
||||
return get_text_stdin(encoding=encoding, errors=errors), False
|
||||
|
||||
# Non-atomic writes directly go out through the regular open functions.
|
||||
if not atomic:
|
||||
if encoding is None:
|
||||
return open(filename, mode), True
|
||||
return io.open(filename, mode, encoding=encoding, errors=errors), True
|
||||
|
||||
# Atomic writes are more complicated. They work by opening a file
|
||||
# as a proxy in the same folder and then using the fdopen
|
||||
# functionality to wrap it in a Python file. Then we wrap it in an
|
||||
# atomic file that moves the file over on close.
|
||||
import tempfile
|
||||
fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename),
|
||||
prefix='.__atomic-write')
|
||||
|
||||
if encoding is not None:
|
||||
f = io.open(fd, mode, encoding=encoding, errors=errors)
|
||||
else:
|
||||
f = os.fdopen(fd, mode)
|
||||
|
||||
return _AtomicFile(f, tmp_filename, filename), True
|
||||
|
||||
|
||||
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||
_rename = os.rename
|
||||
|
||||
|
||||
class _AtomicFile(object):
|
||||
|
||||
def __init__(self, f, tmp_filename, real_filename):
|
||||
self._f = f
|
||||
self._tmp_filename = tmp_filename
|
||||
self._real_filename = real_filename
|
||||
self.closed = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._real_filename
|
||||
|
||||
def close(self, delete=False):
|
||||
if self.closed:
|
||||
return
|
||||
self._f.close()
|
||||
_rename(self._tmp_filename, self._real_filename)
|
||||
self.closed = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._f, name)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close(delete=exc_type is not None)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._f)
|
||||
|
||||
|
||||
auto_wrap_for_ansi = None
|
||||
colorama = None
|
||||
get_winterm_size = None
|
||||
|
||||
|
||||
# If we're on Windows, we provide transparent integration through
|
||||
# colorama. This will make ANSI colors through the echo function
|
||||
# work automatically.
|
||||
if WIN:
|
||||
# Windows has a smaller terminal
|
||||
DEFAULT_COLUMNS = 79
|
||||
|
||||
try:
|
||||
import colorama
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
_ansi_stream_wrappers = WeakKeyDictionary()
|
||||
|
||||
def auto_wrap_for_ansi(stream):
|
||||
"""This function wraps a stream so that calls through colorama
|
||||
are issued to the win32 console API to recolor on demand. It
|
||||
also ensures to reset the colors if a write call is interrupted
|
||||
to not destroy the console afterwards.
|
||||
"""
|
||||
try:
|
||||
cached = _ansi_stream_wrappers.get(stream)
|
||||
except Exception:
|
||||
cached = None
|
||||
if cached is not None:
|
||||
return cached
|
||||
strip = not isatty(stream)
|
||||
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
||||
rv = ansi_wrapper.stream
|
||||
_write = rv.write
|
||||
|
||||
def _safe_write(s):
|
||||
try:
|
||||
return _write(s)
|
||||
except:
|
||||
ansi_wrapper.reset_all()
|
||||
raise
|
||||
|
||||
rv.write = _safe_write
|
||||
try:
|
||||
_ansi_stream_wrappers[stream] = rv
|
||||
except Exception:
|
||||
pass
|
||||
return rv
|
||||
|
||||
def get_winterm_size():
|
||||
win = colorama.win32.GetConsoleScreenBufferInfo(
|
||||
colorama.win32.STDOUT).srWindow
|
||||
return win.Right - win.Left, win.Bottom - win.Top
|
||||
|
||||
|
||||
def strip_ansi(value):
|
||||
return _ansi_re.sub('', value)
|
||||
|
||||
|
||||
def term_len(x):
|
||||
return len(strip_ansi(x))
|
||||
|
||||
|
||||
def isatty(stream):
|
||||
try:
|
||||
return stream.isatty()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _make_cached_stream_func(src_func, wrapper_func):
|
||||
cache = WeakKeyDictionary()
|
||||
def func():
|
||||
stream = src_func()
|
||||
try:
|
||||
rv = cache.get(stream)
|
||||
except Exception:
|
||||
rv = None
|
||||
if rv is not None:
|
||||
return rv
|
||||
rv = wrapper_func()
|
||||
try:
|
||||
cache[stream] = rv
|
||||
except Exception:
|
||||
pass
|
||||
return rv
|
||||
return func
|
||||
|
||||
|
||||
_default_text_stdout = _make_cached_stream_func(
|
||||
lambda: sys.stdout, get_text_stdout)
|
||||
_default_text_stderr = _make_cached_stream_func(
|
||||
lambda: sys.stderr, get_text_stderr)
|
||||
|
||||
|
||||
binary_streams = {
|
||||
'stdin': get_binary_stdin,
|
||||
'stdout': get_binary_stdout,
|
||||
'stderr': get_binary_stderr,
|
||||
}
|
||||
|
||||
text_streams = {
|
||||
'stdin': get_text_stdin,
|
||||
'stdout': get_text_stdout,
|
||||
'stderr': get_text_stderr,
|
||||
}
|
508
click/_termui_impl.py
Normal file
|
@ -0,0 +1,508 @@
|
|||
"""
|
||||
click._termui_impl
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains implementations for the termui module. To keep the
|
||||
import time of Click down, some infrequently used functionality is placed
|
||||
in this module and only imported as needed.
|
||||
|
||||
:copyright: (c) 2014 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import math
|
||||
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
||||
open_stream, strip_ansi, term_len, get_best_encoding, WIN
|
||||
from .utils import echo
|
||||
from .exceptions import ClickException
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
BEFORE_BAR = '\r'
|
||||
AFTER_BAR = '\n'
|
||||
else:
|
||||
BEFORE_BAR = '\r\033[?25l'
|
||||
AFTER_BAR = '\033[?25h\n'
|
||||
|
||||
|
||||
def _length_hint(obj):
|
||||
"""Returns the length hint of an object."""
|
||||
try:
|
||||
return len(obj)
|
||||
except TypeError:
|
||||
try:
|
||||
get_hint = type(obj).__length_hint__
|
||||
except AttributeError:
|
||||
return None
|
||||
try:
|
||||
hint = get_hint(obj)
|
||||
except TypeError:
|
||||
return None
|
||||
if hint is NotImplemented or \
|
||||
not isinstance(hint, (int, long)) or \
|
||||
hint < 0:
|
||||
return None
|
||||
return hint
|
||||
|
||||
|
||||
class ProgressBar(object):
|
||||
|
||||
def __init__(self, iterable, length=None, fill_char='#', empty_char=' ',
|
||||
bar_template='%(bar)s', info_sep=' ', show_eta=True,
|
||||
show_percent=None, show_pos=False, item_show_func=None,
|
||||
label=None, file=None, width=30):
|
||||
self.fill_char = fill_char
|
||||
self.empty_char = empty_char
|
||||
self.bar_template = bar_template
|
||||
self.info_sep = info_sep
|
||||
self.show_eta = show_eta
|
||||
self.show_percent = show_percent
|
||||
self.show_pos = show_pos
|
||||
self.item_show_func = item_show_func
|
||||
self.label = label or ''
|
||||
if file is None:
|
||||
file = _default_text_stdout()
|
||||
self.file = file
|
||||
self.width = width
|
||||
self.autowidth = width == 0
|
||||
|
||||
if length is None:
|
||||
length = _length_hint(iterable)
|
||||
if iterable is None:
|
||||
if length is None:
|
||||
raise TypeError('iterable or length is required')
|
||||
iterable = range_type(length)
|
||||
self.iter = iter(iterable)
|
||||
self.length = length
|
||||
self.length_known = length is not None
|
||||
self.pos = 0
|
||||
self.avg = []
|
||||
self.start = self.last_eta = time.time()
|
||||
self.eta_known = False
|
||||
self.finished = False
|
||||
self.max_width = None
|
||||
self.entered = False
|
||||
self.current_item = None
|
||||
self.is_hidden = not isatty(self.file)
|
||||
|
||||
def __enter__(self):
|
||||
self.entered = True
|
||||
self.render_progress()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.render_finish()
|
||||
|
||||
def __iter__(self):
|
||||
if not self.entered:
|
||||
raise RuntimeError('You need to use progress bars in a with block.')
|
||||
self.render_progress()
|
||||
return self
|
||||
|
||||
def render_finish(self):
|
||||
if self.is_hidden:
|
||||
return
|
||||
self.file.write(AFTER_BAR)
|
||||
self.file.flush()
|
||||
|
||||
@property
|
||||
def pct(self):
|
||||
if self.finished:
|
||||
return 1.0
|
||||
return min(self.pos / (float(self.length) or 1), 1.0)
|
||||
|
||||
@property
|
||||
def time_per_iteration(self):
|
||||
if not self.avg:
|
||||
return 0.0
|
||||
return sum(self.avg) / float(len(self.avg))
|
||||
|
||||
@property
|
||||
def eta(self):
|
||||
if self.length_known and not self.finished:
|
||||
return self.time_per_iteration * (self.length - self.pos)
|
||||
return 0.0
|
||||
|
||||
def format_eta(self):
|
||||
if self.eta_known:
|
||||
return time.strftime('%H:%M:%S', time.gmtime(self.eta + 1))
|
||||
return ''
|
||||
|
||||
def format_pos(self):
|
||||
pos = str(self.pos)
|
||||
if self.length_known:
|
||||
pos += '/%s' % self.length
|
||||
return pos
|
||||
|
||||
def format_pct(self):
|
||||
return ('% 4d%%' % int(self.pct * 100))[1:]
|
||||
|
||||
def format_progress_line(self):
|
||||
show_percent = self.show_percent
|
||||
|
||||
info_bits = []
|
||||
if self.length_known:
|
||||
bar_length = int(self.pct * self.width)
|
||||
bar = self.fill_char * bar_length
|
||||
bar += self.empty_char * (self.width - bar_length)
|
||||
if show_percent is None:
|
||||
show_percent = not self.show_pos
|
||||
else:
|
||||
if self.finished:
|
||||
bar = self.fill_char * self.width
|
||||
else:
|
||||
bar = list(self.empty_char * (self.width or 1))
|
||||
if self.time_per_iteration != 0:
|
||||
bar[int((math.cos(self.pos * self.time_per_iteration)
|
||||
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
||||
bar = ''.join(bar)
|
||||
|
||||
if self.show_pos:
|
||||
info_bits.append(self.format_pos())
|
||||
if show_percent:
|
||||
info_bits.append(self.format_pct())
|
||||
if self.show_eta and self.eta_known and not self.finished:
|
||||
info_bits.append(self.format_eta())
|
||||
if self.item_show_func is not None:
|
||||
item_info = self.item_show_func(self.current_item)
|
||||
if item_info is not None:
|
||||
info_bits.append(item_info)
|
||||
|
||||
return (self.bar_template % {
|
||||
'label': self.label,
|
||||
'bar': bar,
|
||||
'info': self.info_sep.join(info_bits)
|
||||
}).rstrip()
|
||||
|
||||
def render_progress(self):
|
||||
from .termui import get_terminal_size
|
||||
|
||||
if self.is_hidden:
|
||||
echo(self.label, file=self.file)
|
||||
self.file.flush()
|
||||
return
|
||||
|
||||
# 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:
|
||||
self.file.write(BEFORE_BAR)
|
||||
self.file.write(' ' * self.max_width)
|
||||
self.max_width = new_width
|
||||
self.width = new_width
|
||||
|
||||
clear_width = self.width
|
||||
if self.max_width is not None:
|
||||
clear_width = self.max_width
|
||||
|
||||
self.file.write(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
|
||||
# Use echo here so that we get colorama support.
|
||||
echo(line, file=self.file, nl=False)
|
||||
self.file.write(' ' * (clear_width - line_len))
|
||||
self.file.flush()
|
||||
|
||||
def make_step(self):
|
||||
self.pos += 1
|
||||
if self.length_known and self.pos >= self.length:
|
||||
self.finished = True
|
||||
|
||||
if (time.time() - self.last_eta) < 1.0:
|
||||
return
|
||||
|
||||
self.last_eta = time.time()
|
||||
self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)]
|
||||
|
||||
self.eta_known = self.length_known
|
||||
|
||||
def finish(self):
|
||||
self.eta_known = 0
|
||||
self.current_item = None
|
||||
self.finished = True
|
||||
|
||||
def next(self):
|
||||
if self.is_hidden:
|
||||
return next(self.iter)
|
||||
try:
|
||||
rv = next(self.iter)
|
||||
self.current_item = rv
|
||||
except StopIteration:
|
||||
self.finish()
|
||||
self.render_progress()
|
||||
raise StopIteration()
|
||||
else:
|
||||
self.make_step()
|
||||
self.render_progress()
|
||||
return rv
|
||||
|
||||
if not PY2:
|
||||
__next__ = next
|
||||
del next
|
||||
|
||||
|
||||
def pager(text, color=None):
|
||||
"""Decide what method to use for paging through text."""
|
||||
stdout = _default_text_stdout()
|
||||
if not isatty(sys.stdin) or not isatty(stdout):
|
||||
return _nullpager(stdout, text, color)
|
||||
if 'PAGER' in os.environ:
|
||||
if WIN:
|
||||
return _tempfilepager(text, os.environ['PAGER'], color)
|
||||
return _pipepager(text, os.environ['PAGER'], color)
|
||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||
return _nullpager(stdout, text, color)
|
||||
if WIN or sys.platform.startswith('os2'):
|
||||
return _tempfilepager(text, 'more <', color)
|
||||
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
||||
return _pipepager(text, 'less', color)
|
||||
|
||||
import tempfile
|
||||
fd, filename = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
try:
|
||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
||||
return _pipepager(text, 'more', color)
|
||||
return _nullpager(stdout, text, color)
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def _pipepager(text, cmd, color):
|
||||
"""Page through text by feeding it to another program. Invoking a
|
||||
pager through this might support colors.
|
||||
"""
|
||||
import subprocess
|
||||
env = dict(os.environ)
|
||||
|
||||
# If we're piping to less we might support colors under the
|
||||
# condition that
|
||||
cmd_detail = cmd.rsplit('/', 1)[-1].split()
|
||||
if color is None and cmd_detail[0] == 'less':
|
||||
less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:])
|
||||
if not less_flags:
|
||||
env['LESS'] = '-R'
|
||||
color = True
|
||||
elif 'r' in less_flags or 'R' in less_flags:
|
||||
color = True
|
||||
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
|
||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||
env=env)
|
||||
encoding = get_best_encoding(c.stdin)
|
||||
try:
|
||||
c.stdin.write(text.encode(encoding, 'replace'))
|
||||
c.stdin.close()
|
||||
except IOError:
|
||||
pass
|
||||
c.wait()
|
||||
|
||||
|
||||
def _tempfilepager(text, cmd, color):
|
||||
"""Page through text by invoking a program on a temporary file."""
|
||||
import tempfile
|
||||
filename = tempfile.mktemp()
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
encoding = get_best_encoding(sys.stdout)
|
||||
with open_stream(filename, 'wb')[0] as f:
|
||||
f.write(text.encode(encoding))
|
||||
try:
|
||||
os.system(cmd + ' "' + filename + '"')
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def _nullpager(stream, text, color):
|
||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
stream.write(text)
|
||||
|
||||
|
||||
class Editor(object):
|
||||
|
||||
def __init__(self, editor=None, env=None, require_save=True,
|
||||
extension='.txt'):
|
||||
self.editor = editor
|
||||
self.env = env
|
||||
self.require_save = require_save
|
||||
self.extension = extension
|
||||
|
||||
def get_editor(self):
|
||||
if self.editor is not None:
|
||||
return self.editor
|
||||
for key in 'VISUAL', 'EDITOR':
|
||||
rv = os.environ.get(key)
|
||||
if rv:
|
||||
return rv
|
||||
if WIN:
|
||||
return 'notepad'
|
||||
for editor in 'vim', 'nano':
|
||||
if os.system('which %s &> /dev/null' % editor) == 0:
|
||||
return editor
|
||||
return 'vi'
|
||||
|
||||
def edit_file(self, filename):
|
||||
import subprocess
|
||||
editor = self.get_editor()
|
||||
if self.env:
|
||||
environ = os.environ.copy()
|
||||
environ.update(self.env)
|
||||
else:
|
||||
environ = None
|
||||
try:
|
||||
c = subprocess.Popen('%s "%s"' % (editor, filename),
|
||||
env=environ, shell=True)
|
||||
exit_code = c.wait()
|
||||
if exit_code != 0:
|
||||
raise ClickException('%s: Editing failed!' % editor)
|
||||
except OSError as e:
|
||||
raise ClickException('%s: Editing failed: %s' % (editor, e))
|
||||
|
||||
def edit(self, text):
|
||||
import tempfile
|
||||
|
||||
text = text or ''
|
||||
if text and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
|
||||
fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension)
|
||||
try:
|
||||
if WIN:
|
||||
encoding = 'utf-8-sig'
|
||||
text = text.replace('\n', '\r\n')
|
||||
else:
|
||||
encoding = 'utf-8'
|
||||
text = text.encode(encoding)
|
||||
|
||||
f = os.fdopen(fd, 'wb')
|
||||
f.write(text)
|
||||
f.close()
|
||||
timestamp = os.path.getmtime(name)
|
||||
|
||||
self.edit_file(name)
|
||||
|
||||
if self.require_save \
|
||||
and os.path.getmtime(name) == timestamp:
|
||||
return None
|
||||
|
||||
f = open(name, 'rb')
|
||||
try:
|
||||
rv = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
return rv.decode('utf-8-sig').replace('\r\n', '\n')
|
||||
finally:
|
||||
os.unlink(name)
|
||||
|
||||
|
||||
def open_url(url, wait=False, locate=False):
|
||||
import subprocess
|
||||
|
||||
def _unquote_file(url):
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
import urllib
|
||||
if url.startswith('file://'):
|
||||
url = urllib.unquote(url[7:])
|
||||
return url
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
args = ['open']
|
||||
if wait:
|
||||
args.append('-W')
|
||||
if locate:
|
||||
args.append('-R')
|
||||
args.append(_unquote_file(url))
|
||||
null = open('/dev/null', 'w')
|
||||
try:
|
||||
return subprocess.Popen(args, stderr=null).wait()
|
||||
finally:
|
||||
null.close()
|
||||
elif WIN:
|
||||
if locate:
|
||||
url = _unquote_file(url)
|
||||
args = 'explorer /select,"%s"' % _unquote_file(
|
||||
url.replace('"', ''))
|
||||
else:
|
||||
args = 'start %s "" "%s"' % (
|
||||
wait and '/WAIT' or '', url.replace('"', ''))
|
||||
return os.system(args)
|
||||
|
||||
try:
|
||||
if locate:
|
||||
url = os.path.dirname(_unquote_file(url)) or '.'
|
||||
else:
|
||||
url = _unquote_file(url)
|
||||
c = subprocess.Popen(['xdg-open', url])
|
||||
if wait:
|
||||
return c.wait()
|
||||
return 0
|
||||
except OSError:
|
||||
if url.startswith(('http://', 'https://')) and not locate and not wait:
|
||||
import webbrowser
|
||||
webbrowser.open(url)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def _translate_ch_to_exc(ch):
|
||||
if ch == '\x03':
|
||||
raise KeyboardInterrupt()
|
||||
if ch == '\x04':
|
||||
raise EOFError()
|
||||
|
||||
|
||||
if WIN:
|
||||
import msvcrt
|
||||
|
||||
def getchar(echo):
|
||||
rv = msvcrt.getch()
|
||||
if echo:
|
||||
msvcrt.putchar(rv)
|
||||
_translate_ch_to_exc(rv)
|
||||
if PY2:
|
||||
enc = getattr(sys.stdin, 'encoding', None)
|
||||
if enc is not None:
|
||||
rv = rv.decode(enc, 'replace')
|
||||
else:
|
||||
rv = rv.decode('cp1252', 'replace')
|
||||
return rv
|
||||
else:
|
||||
import tty
|
||||
import termios
|
||||
|
||||
def getchar(echo):
|
||||
if not isatty(sys.stdin):
|
||||
f = open('/dev/tty')
|
||||
fd = f.fileno()
|
||||
else:
|
||||
fd = sys.stdin.fileno()
|
||||
f = None
|
||||
try:
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
ch = os.read(fd, 32)
|
||||
if echo and isatty(sys.stdout):
|
||||
sys.stdout.write(ch)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
sys.stdout.flush()
|
||||
if f is not None:
|
||||
f.close()
|
||||
except termios.error:
|
||||
pass
|
||||
_translate_ch_to_exc(ch)
|
||||
return ch.decode(get_best_encoding(sys.stdin), 'replace')
|
38
click/_textwrap.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import textwrap
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
class TextWrapper(textwrap.TextWrapper):
|
||||
|
||||
def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
|
||||
space_left = max(width - cur_len, 1)
|
||||
|
||||
if self.break_long_words:
|
||||
last = reversed_chunks[-1]
|
||||
cut = last[:space_left]
|
||||
res = last[space_left:]
|
||||
cur_line.append(cut)
|
||||
reversed_chunks[-1] = res
|
||||
elif not cur_line:
|
||||
cur_line.append(reversed_chunks.pop())
|
||||
|
||||
@contextmanager
|
||||
def extra_indent(self, indent):
|
||||
old_initial_indent = self.initial_indent
|
||||
old_subsequent_indent = self.subsequent_indent
|
||||
self.initial_indent += indent
|
||||
self.subsequent_indent += indent
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.initial_indent = old_initial_indent
|
||||
self.subsequent_indent = old_subsequent_indent
|
||||
|
||||
def indent_only(self, text):
|
||||
rv = []
|
||||
for idx, line in enumerate(text.splitlines()):
|
||||
indent = self.initial_indent
|
||||
if idx > 0:
|
||||
indent = self.subsequent_indent
|
||||
rv.append(indent + line)
|
||||
return '\n'.join(rv)
|
1575
click/core.py
Normal file
|
@ -0,0 +1,1575 @@
|
|||
import os
|
||||
import sys
|
||||
import codecs
|
||||
from contextlib import contextmanager
|
||||
from itertools import chain, repeat
|
||||
from functools import update_wrapper
|
||||
|
||||
from .types import convert_type, IntRange, BOOL
|
||||
from .utils import make_str, make_default_short_help, echo
|
||||
from .exceptions import ClickException, UsageError, BadParameter, Abort
|
||||
from .termui import prompt, confirm
|
||||
from .formatting import HelpFormatter, join_options
|
||||
from .parser import OptionParser, split_opt
|
||||
|
||||
from ._compat import PY2, isidentifier, iteritems
|
||||
|
||||
_missing = object()
|
||||
|
||||
|
||||
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
|
||||
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
|
||||
|
||||
|
||||
def _bashcomplete(cmd, prog_name, complete_var=None):
|
||||
"""Internal handler for the bash completion support."""
|
||||
if complete_var is None:
|
||||
complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper()
|
||||
complete_instr = os.environ.get(complete_var)
|
||||
if not complete_instr:
|
||||
return
|
||||
|
||||
from ._bashcomplete import bashcomplete
|
||||
if bashcomplete(cmd, prog_name, complete_var, complete_instr):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def batch(iterable, batch_size):
|
||||
return list(zip(*repeat(iter(iterable), batch_size)))
|
||||
|
||||
|
||||
def invoke_param_callback(callback, ctx, param, value):
|
||||
code = getattr(callback, '__code__', None)
|
||||
args = getattr(code, 'co_argcount', 3)
|
||||
|
||||
if args < 3:
|
||||
# This will become a warning in Click 3.0:
|
||||
from warnings import warn
|
||||
warn(Warning('Invoked legacy parameter callback "%s". The new '
|
||||
'signature for such callbacks starting with '
|
||||
'click 2.0 is (ctx, param, value).'
|
||||
% callback), stacklevel=3)
|
||||
return callback(ctx, value)
|
||||
return callback(ctx, param, value)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def augment_usage_errors(ctx, param=None):
|
||||
"""Context manager that attaches extra information to exceptions that
|
||||
fly.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except BadParameter as e:
|
||||
if e.ctx is None:
|
||||
e.ctx = ctx
|
||||
if param is not None and e.param is None:
|
||||
e.param = param
|
||||
raise
|
||||
except UsageError as e:
|
||||
if e.ctx is None:
|
||||
e.ctx = ctx
|
||||
raise
|
||||
|
||||
|
||||
def iter_params_for_processing(invocation_order, declaration_order):
|
||||
"""Given a sequence of parameters in the order as should be considered
|
||||
for processing and an iterable of parameters that exist, this returns
|
||||
a list in the correct order as they should be processed.
|
||||
"""
|
||||
def sort_key(item):
|
||||
try:
|
||||
idx = invocation_order.index(item)
|
||||
except ValueError:
|
||||
idx = float('inf')
|
||||
return (not item.is_eager, idx)
|
||||
|
||||
return sorted(declaration_order, key=sort_key)
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""The context is a special internal object that holds state relevant
|
||||
for the script execution at every single level. It's normally invisible
|
||||
to commands unless they opt-in to getting access to it.
|
||||
|
||||
The context is useful as it can pass internal objects around and can
|
||||
control special execution features such as reading data from
|
||||
environment variables.
|
||||
|
||||
A context can be used as context manager in which case it will call
|
||||
:meth:`close` on teardown.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
Added the `resilient_parsing`, `help_option_names`,
|
||||
`token_normalize_func` parameters.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
Added the `allow_extra_args` and `allow_interspersed_args`
|
||||
parameters.
|
||||
|
||||
:param command: the command class for this context.
|
||||
:param parent: the parent context.
|
||||
:param info_name: the info name for this invokation. Generally this
|
||||
is the most descriptive name for the script or
|
||||
command. For the toplevel script is is usually
|
||||
the name of the script, for commands below it it's
|
||||
the name of the script.
|
||||
:param obj: an arbitrary object of user data.
|
||||
:param auto_envvar_prefix: the prefix to use for automatic environment
|
||||
variables. If this is `None` then reading
|
||||
from environment variables is disabled. This
|
||||
does not affect manually set environment
|
||||
variables which are always read.
|
||||
:param default_map: a dictionary (like object) with default values
|
||||
for parameters.
|
||||
:param terminal_width: the width of the terminal. The default is
|
||||
inherit from parent context. If no context
|
||||
defines the terminal width then auto
|
||||
detection will be applied.
|
||||
:param resilient_parsing: if this flag is enabled then Click will
|
||||
parse without any interactivity or callback
|
||||
invocation. This is useful for implementing
|
||||
things such as completion support.
|
||||
:param allow_extra_args: if this is set to `True` then extra arguments
|
||||
at the end will not raise an error and will be
|
||||
kept on the context. The default is to inherit
|
||||
from the command.
|
||||
:param allow_interspersed_args: if this is set to `False` then options
|
||||
and arguments cannot be mixed. The
|
||||
default is to inherit from the command.
|
||||
:param help_option_names: optionally a list of strings that define how
|
||||
the default help parameter is named. The
|
||||
default is ``['--help']``.
|
||||
:param token_normalize_func: an optional function that is used to
|
||||
normalize tokens (options, choices,
|
||||
etc.). This for instance can be used to
|
||||
implement case insensitive behavior.
|
||||
"""
|
||||
|
||||
def __init__(self, command, parent=None, info_name=None, obj=None,
|
||||
auto_envvar_prefix=None, default_map=None,
|
||||
terminal_width=None, resilient_parsing=False,
|
||||
allow_extra_args=None, allow_interspersed_args=None,
|
||||
help_option_names=None, token_normalize_func=None):
|
||||
#: the parent context or `None` if none exists.
|
||||
self.parent = parent
|
||||
#: the :class:`Command` for this context.
|
||||
self.command = command
|
||||
#: the descriptive information name
|
||||
self.info_name = info_name
|
||||
#: the parsed parameters except if the value is hidden in which
|
||||
#: case it's not remembered.
|
||||
self.params = {}
|
||||
#: the leftover arguments.
|
||||
self.args = []
|
||||
if obj is None and parent is not None:
|
||||
obj = parent.obj
|
||||
#: the user object stored.
|
||||
self.obj = obj
|
||||
#: A dictionary (-like object) with defaults for parameters.
|
||||
if default_map is None \
|
||||
and parent is not None \
|
||||
and parent.default_map is not None:
|
||||
default_map = parent.default_map.get(info_name)
|
||||
self.default_map = default_map
|
||||
|
||||
#: This flag indicates if a subcommand is going to be executed. A
|
||||
#: group callback can use this information to figure out if it's
|
||||
#: being executed directly or because the execution flow passes
|
||||
#: onwards to a subcommand. By default it's None, but it can be
|
||||
#: the name of the subcommand to execute.
|
||||
#:
|
||||
#: If chaining is enabled this will be set to ``'*'`` in case
|
||||
#: any commands are executed. It is however not possible to
|
||||
#: figure out which ones. If you require this knowledge you
|
||||
#: should use a :func:`resultcallback`.
|
||||
self.invoked_subcommand = None
|
||||
|
||||
if terminal_width is None and parent is not None:
|
||||
terminal_width = parent.terminal_width
|
||||
#: The width of the terminal (None is autodetection).
|
||||
self.terminal_width = terminal_width
|
||||
|
||||
if allow_extra_args is None:
|
||||
allow_extra_args = command.allow_extra_args
|
||||
#: Indicates if the context allows extra args or if it should
|
||||
#: fail on parsing.
|
||||
#:
|
||||
#: .. versionadded:: 3.0
|
||||
self.allow_extra_args = allow_extra_args
|
||||
|
||||
if allow_interspersed_args is None:
|
||||
allow_interspersed_args = command.allow_interspersed_args
|
||||
#: Indicates if the context allows mixing of arguments and
|
||||
#: options or not.
|
||||
#:
|
||||
#: .. versionadded:: 3.0
|
||||
self.allow_interspersed_args = allow_interspersed_args
|
||||
|
||||
if help_option_names is None:
|
||||
if parent is not None:
|
||||
help_option_names = parent.help_option_names
|
||||
else:
|
||||
help_option_names = ['--help']
|
||||
|
||||
#: The names for the help options.
|
||||
self.help_option_names = help_option_names
|
||||
|
||||
if token_normalize_func is None and parent is not None:
|
||||
token_normalize_func = parent.token_normalize_func
|
||||
|
||||
#: An optional normalization function for tokens. This is
|
||||
#: options, choices, commands etc.
|
||||
self.token_normalize_func = token_normalize_func
|
||||
|
||||
#: Indicates if resilient parsing is enabled. In that case Click
|
||||
#: will do its best to not cause any failures.
|
||||
self.resilient_parsing = resilient_parsing
|
||||
|
||||
# If there is no envvar prefix yet, but the parent has one and
|
||||
# the command on this level has a name, we can expand the envvar
|
||||
# prefix automatically.
|
||||
if auto_envvar_prefix is None:
|
||||
if parent is not None \
|
||||
and parent.auto_envvar_prefix is not None and \
|
||||
self.info_name is not None:
|
||||
auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix,
|
||||
self.info_name.upper())
|
||||
else:
|
||||
self.auto_envvar_prefix = auto_envvar_prefix.upper()
|
||||
self.auto_envvar_prefix = auto_envvar_prefix
|
||||
|
||||
self._close_callbacks = []
|
||||
self._depth = 0
|
||||
|
||||
def __enter__(self):
|
||||
self._depth += 1
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self._depth -= 1
|
||||
if self._depth == 0:
|
||||
self.close()
|
||||
|
||||
def _get_invoked_subcommands(self):
|
||||
from warnings import warn
|
||||
warn(Warning('This API does not work properly and has been largely '
|
||||
'removed in Click 3.2 to fix a regression the '
|
||||
'introduction of this API caused. Consult the '
|
||||
'upgrade documentation for more information. For '
|
||||
'more information about this see '
|
||||
'http://click.pocoo.org/upgrading/#upgrading-to-3.2'),
|
||||
stacklevel=2)
|
||||
if self.invoked_subcommand is None:
|
||||
return []
|
||||
return [self.invoked_subcommand]
|
||||
def _set_invoked_subcommands(self, value):
|
||||
self.invoked_subcommand = \
|
||||
len(value) > 1 and '*' or value and value[0] or None
|
||||
invoked_subcommands = property(_get_invoked_subcommands,
|
||||
_set_invoked_subcommands)
|
||||
del _get_invoked_subcommands, _set_invoked_subcommands
|
||||
|
||||
def make_formatter(self):
|
||||
"""Creates the formatter for the help and usage output."""
|
||||
return HelpFormatter(width=self.terminal_width)
|
||||
|
||||
def call_on_close(self, f):
|
||||
"""This decorator remembers a function as callback that should be
|
||||
executed when the context tears down. This is most useful to bind
|
||||
resource handling to the script execution. For instance, file objects
|
||||
opened by the :class:`File` type will register their close callbacks
|
||||
here.
|
||||
|
||||
:param f: the function to execute on teardown.
|
||||
"""
|
||||
self._close_callbacks.append(f)
|
||||
return f
|
||||
|
||||
def close(self):
|
||||
"""Invokes all close callbacks."""
|
||||
for cb in self._close_callbacks:
|
||||
cb()
|
||||
self._close_callbacks = []
|
||||
|
||||
@property
|
||||
def command_path(self):
|
||||
"""The computed command path. This is used for the ``usage``
|
||||
information on the help page. It's automatically created by
|
||||
combining the info names of the chain of contexts to the root.
|
||||
"""
|
||||
rv = ''
|
||||
if self.info_name is not None:
|
||||
rv = self.info_name
|
||||
if self.parent is not None:
|
||||
rv = self.parent.command_path + ' ' + rv
|
||||
return rv.lstrip()
|
||||
|
||||
def find_root(self):
|
||||
"""Finds the outermost context."""
|
||||
node = self
|
||||
while node.parent is not None:
|
||||
node = node.parent
|
||||
return node
|
||||
|
||||
def find_object(self, object_type):
|
||||
"""Finds the closest object of a given type."""
|
||||
node = self
|
||||
while node is not None:
|
||||
if isinstance(node.obj, object_type):
|
||||
return node.obj
|
||||
node = node.parent
|
||||
|
||||
def ensure_object(self, object_type):
|
||||
"""Like :meth:`find_object` but sets the innermost object to a
|
||||
new instance of `object_type` if it does not exist.
|
||||
"""
|
||||
rv = self.find_object(object_type)
|
||||
if rv is None:
|
||||
self.obj = rv = object_type()
|
||||
return rv
|
||||
|
||||
def lookup_default(self, name):
|
||||
"""Looks up the default for a parameter name. This by default
|
||||
looks into the :attr:`default_map` if available.
|
||||
"""
|
||||
if self.default_map is not None:
|
||||
rv = self.default_map.get(name)
|
||||
if callable(rv):
|
||||
rv = rv()
|
||||
return rv
|
||||
|
||||
def fail(self, message):
|
||||
"""Aborts the execution of the program with a specific error
|
||||
message.
|
||||
|
||||
:param message: the error message to fail with.
|
||||
"""
|
||||
raise UsageError(message, self)
|
||||
|
||||
def abort(self):
|
||||
"""Aborts the script."""
|
||||
raise Abort()
|
||||
|
||||
def exit(self, code=0):
|
||||
"""Exits the application with a given exit code."""
|
||||
sys.exit(code)
|
||||
|
||||
def get_usage(self):
|
||||
"""Helper method to get formatted usage string for the current
|
||||
context and command.
|
||||
"""
|
||||
return self.command.get_usage(self)
|
||||
|
||||
def get_help(self):
|
||||
"""Helper method to get formatted help page for the current
|
||||
context and command.
|
||||
"""
|
||||
return self.command.get_help(self)
|
||||
|
||||
def invoke(*args, **kwargs):
|
||||
"""Invokes a command callback in exactly the way it expects. There
|
||||
are two ways to invoke this method:
|
||||
|
||||
1. the first argument can be a callback and all other arguments and
|
||||
keyword arguments are forwarded directly to the function.
|
||||
2. the first argument is a click command object. In that case all
|
||||
arguments are forwarded as well but proper click parameters
|
||||
(options and click arguments) must be keyword arguments and Click
|
||||
will fill in defaults.
|
||||
|
||||
Note that before Click 3.2 keyword arguments were not properly filled
|
||||
in against the intention of this code and no context was created. For
|
||||
more information about this change and why it was done in a bugfix
|
||||
release see :ref:`upgrade-to-3.2`.
|
||||
"""
|
||||
self, callback = args[:2]
|
||||
ctx = self
|
||||
|
||||
# This is just to improve the error message in cases where old
|
||||
# code incorrectly invoked this method. This will eventually be
|
||||
# removed.
|
||||
injected_arguments = False
|
||||
|
||||
# It's also possible to invoke another command which might or
|
||||
# might not have a callback. In that case we also fill
|
||||
# in defaults and make a new context for this command.
|
||||
if isinstance(callback, Command):
|
||||
other_cmd = callback
|
||||
callback = other_cmd.callback
|
||||
ctx = Context(other_cmd, info_name=other_cmd.name, parent=self)
|
||||
if callback is None:
|
||||
raise TypeError('The given command does not have a '
|
||||
'callback that can be invoked.')
|
||||
|
||||
for param in other_cmd.params:
|
||||
if param.name not in kwargs and param.expose_value:
|
||||
kwargs[param.name] = param.get_default(ctx)
|
||||
injected_arguments = True
|
||||
|
||||
args = args[2:]
|
||||
if getattr(callback, '__click_pass_context__', False):
|
||||
args = (ctx,) + args
|
||||
with augment_usage_errors(self):
|
||||
try:
|
||||
with ctx:
|
||||
return callback(*args, **kwargs)
|
||||
except TypeError as e:
|
||||
if not injected_arguments:
|
||||
raise
|
||||
if 'got multiple values for' in str(e):
|
||||
raise RuntimeError(
|
||||
'You called .invoke() on the context with a command '
|
||||
'but provided parameters as positional arguments. '
|
||||
'This is not supported but sometimes worked by chance '
|
||||
'in older versions of Click. To fix this see '
|
||||
'http://click.pocoo.org/upgrading/#upgrading-to-3.2')
|
||||
raise
|
||||
|
||||
def forward(*args, **kwargs):
|
||||
"""Similar to :meth:`invoke` but fills in default keyword
|
||||
arguments from the current context if the other command expects
|
||||
it. This cannot invoke callbacks directly, only other commands.
|
||||
"""
|
||||
self, cmd = args[:2]
|
||||
|
||||
# It's also possible to invoke another command which might or
|
||||
# might not have a callback.
|
||||
if not isinstance(cmd, Command):
|
||||
raise TypeError('Callback is not a command.')
|
||||
|
||||
for param in self.params:
|
||||
if param not in kwargs:
|
||||
kwargs[param] = self.params[param]
|
||||
|
||||
return self.invoke(cmd, **kwargs)
|
||||
|
||||
|
||||
class BaseCommand(object):
|
||||
"""The base command implements the minimal API contract of commands.
|
||||
Most code will never use this as it does not implement a lot of useful
|
||||
functionality but it can act as the direct subclass of alternative
|
||||
parsing methods that do not depend on the Click parser.
|
||||
|
||||
For instance, this can be used to bridge Click and other systems like
|
||||
argparse or docopt.
|
||||
|
||||
Because base commands do not implement a lot of the API that other
|
||||
parts of Click take for granted, they are not supported for all
|
||||
operations. For instance, they cannot be used with the decorators
|
||||
usually and they have no built-in callback system.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Added the `context_settings` parameter.
|
||||
|
||||
:param name: the name of the command to use unless a group overrides it.
|
||||
:param context_settings: an optional dictionary with defaults that are
|
||||
passed to the context object.
|
||||
"""
|
||||
allow_extra_args = False
|
||||
allow_interspersed_args = True
|
||||
|
||||
def __init__(self, name, context_settings=None):
|
||||
#: the name the command thinks it has. Upon registering a command
|
||||
#: on a :class:`Group` the group will default the command name
|
||||
#: with this information. You should instead use the
|
||||
#: :class:`Context`\'s :attr:`~Context.info_name` attribute.
|
||||
self.name = name
|
||||
#: an optional dictionary with defaults passed to the context.
|
||||
self.context_settings = context_settings
|
||||
|
||||
def get_usage(self, ctx):
|
||||
raise NotImplementedError('Base commands cannot get usage')
|
||||
|
||||
def get_help(self, ctx):
|
||||
raise NotImplementedError('Base commands cannot get help')
|
||||
|
||||
def make_context(self, info_name, args, parent=None, **extra):
|
||||
"""This function when given an info name and arguments will kick
|
||||
off the parsing and create a new :class:`Context`. It does not
|
||||
invoke the actual command callback though.
|
||||
|
||||
:param info_name: the info name for this invokation. Generally this
|
||||
is the most descriptive name for the script or
|
||||
command. For the toplevel script it's usually
|
||||
the name of the script, for commands below it it's
|
||||
the name of the script.
|
||||
:param args: the arguments to parse as list of strings.
|
||||
:param parent: the parent context if available.
|
||||
:param extra: extra keyword arguments forwarded to the context
|
||||
constructor.
|
||||
"""
|
||||
for key, value in iteritems(self.context_settings or {}):
|
||||
if key not in extra:
|
||||
extra[key] = value
|
||||
ctx = Context(self, info_name=info_name, parent=parent, **extra)
|
||||
self.parse_args(ctx, args)
|
||||
return ctx
|
||||
|
||||
def parse_args(self, ctx, args):
|
||||
"""Given a context and a list of arguments this creates the parser
|
||||
and parses the arguments, then modifies the context as necessary.
|
||||
This is automatically invoked by :meth:`make_context`.
|
||||
"""
|
||||
raise NotImplementedError('Base commands do not know how to parse '
|
||||
'arguments.')
|
||||
|
||||
def invoke(self, ctx):
|
||||
"""Given a context, this invokes the command. The default
|
||||
implementation is raising a not implemented error.
|
||||
"""
|
||||
raise NotImplementedError('Base commands are not invokable by default')
|
||||
|
||||
def main(self, args=None, prog_name=None, complete_var=None,
|
||||
standalone_mode=True, **extra):
|
||||
"""This is the way to invoke a script with all the bells and
|
||||
whistles as a command line application. This will always terminate
|
||||
the application after a call. If this is not wanted, ``SystemExit``
|
||||
needs to be caught.
|
||||
|
||||
This method is also available by directly calling the instance of
|
||||
a :class:`Command`.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
Added the `standalone_mode` flag to control the standalone mode.
|
||||
|
||||
:param args: the arguments that should be used for parsing. If not
|
||||
provided, ``sys.argv[1:]`` is used.
|
||||
:param prog_name: the program name that should be used. By default
|
||||
the program name is constructed by taking the file
|
||||
name from ``sys.argv[0]``.
|
||||
:param complete_var: the environment variable that controls the
|
||||
bash completion support. The default is
|
||||
``"_<prog_name>_COMPLETE"`` with prog name in
|
||||
uppercase.
|
||||
:param standalone_mode: the default behavior is to invoke the script
|
||||
in standalone mode. Click will then
|
||||
handle exceptions and convert them into
|
||||
error messages and the function will never
|
||||
return but shut down the interpreter. If
|
||||
this is set to `False` they will be
|
||||
propagated to the caller and the return
|
||||
value of this function is the return value
|
||||
of :meth:`invoke`.
|
||||
:param extra: extra keyword arguments are forwarded to the context
|
||||
constructor. See :class:`Context` for more information.
|
||||
"""
|
||||
# If we are in Python 3, we will verify that the environment is
|
||||
# sane at this point of reject further execution to avoid a
|
||||
# broken script.
|
||||
if not PY2:
|
||||
try:
|
||||
import locale
|
||||
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
||||
except Exception:
|
||||
fs_enc = 'ascii'
|
||||
if fs_enc == 'ascii':
|
||||
raise RuntimeError('Click will abort further execution '
|
||||
'because Python 3 was configured to use '
|
||||
'ASCII as encoding for the environment. '
|
||||
'Either switch to Python 2 or consult '
|
||||
'http://click.pocoo.org/python3/ '
|
||||
'for mitigation steps.')
|
||||
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
else:
|
||||
args = list(args)
|
||||
if prog_name is None:
|
||||
prog_name = make_str(os.path.basename(
|
||||
sys.argv and sys.argv[0] or __file__))
|
||||
|
||||
# Hook for the Bash completion. This only activates if the Bash
|
||||
# completion is actually enabled, otherwise this is quite a fast
|
||||
# noop.
|
||||
_bashcomplete(self, prog_name, complete_var)
|
||||
|
||||
try:
|
||||
try:
|
||||
with self.make_context(prog_name, args, **extra) as ctx:
|
||||
rv = self.invoke(ctx)
|
||||
if not standalone_mode:
|
||||
return rv
|
||||
ctx.exit()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
echo(file=sys.stderr)
|
||||
raise Abort()
|
||||
except ClickException as e:
|
||||
if not standalone_mode:
|
||||
raise
|
||||
e.show()
|
||||
sys.exit(e.exit_code)
|
||||
except Abort:
|
||||
if not standalone_mode:
|
||||
raise
|
||||
echo('Aborted!', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Alias for :meth:`main`."""
|
||||
return self.main(*args, **kwargs)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Commands are the basic building block of command line interfaces in
|
||||
Click. A basic command handles command line parsing and might dispatch
|
||||
more parsing to commands nested below it.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Added the `context_settings` parameter.
|
||||
|
||||
:param name: the name of the command to use unless a group overrides it.
|
||||
:param context_settings: an optional dictionary with defaults that are
|
||||
passed to the context object.
|
||||
:param callback: the callback to invoke. This is optional.
|
||||
:param params: the parameters to register with this command. This can
|
||||
be either :class:`Option` or :class:`Argument` objects.
|
||||
:param help: the help string to use for this command.
|
||||
:param epilog: like the help string but it's printed at the end of the
|
||||
help page after everything else.
|
||||
:param short_help: the short help to use for this command. This is
|
||||
shown on the command listing of the parent command.
|
||||
:param add_help_option: by default each command registers a ``--help``
|
||||
option. This can be disabled by this parameter.
|
||||
"""
|
||||
|
||||
def __init__(self, name, context_settings=None, callback=None,
|
||||
params=None, help=None, epilog=None, short_help=None,
|
||||
options_metavar='[OPTIONS]', add_help_option=True):
|
||||
BaseCommand.__init__(self, name, context_settings)
|
||||
#: the callback to execute when the command fires. This might be
|
||||
#: `None` in which case nothing happens.
|
||||
self.callback = callback
|
||||
#: the list of parameters for this command in the order they
|
||||
#: should show up in the help page and execute. Eager parameters
|
||||
#: will automatically be handled before non eager ones.
|
||||
self.params = params or []
|
||||
self.help = help
|
||||
self.epilog = epilog
|
||||
self.options_metavar = options_metavar
|
||||
if short_help is None and help:
|
||||
short_help = make_default_short_help(help)
|
||||
self.short_help = short_help
|
||||
self.add_help_option = add_help_option
|
||||
|
||||
def get_usage(self, ctx):
|
||||
formatter = ctx.make_formatter()
|
||||
self.format_usage(ctx, formatter)
|
||||
return formatter.getvalue().rstrip('\n')
|
||||
|
||||
def get_params(self, ctx):
|
||||
rv = self.params
|
||||
help_option = self.get_help_option(ctx)
|
||||
if help_option is not None:
|
||||
rv = rv + [help_option]
|
||||
return rv
|
||||
|
||||
def format_usage(self, ctx, formatter):
|
||||
"""Writes the usage line into the formatter."""
|
||||
pieces = self.collect_usage_pieces(ctx)
|
||||
formatter.write_usage(ctx.command_path, ' '.join(pieces))
|
||||
|
||||
def collect_usage_pieces(self, ctx):
|
||||
"""Returns all the pieces that go into the usage line and returns
|
||||
it as a list of strings.
|
||||
"""
|
||||
rv = [self.options_metavar]
|
||||
for param in self.get_params(ctx):
|
||||
rv.extend(param.get_usage_pieces(ctx))
|
||||
return rv
|
||||
|
||||
def get_help_option_names(self, ctx):
|
||||
"""Returns the names for the help option."""
|
||||
all_names = set(ctx.help_option_names)
|
||||
for param in self.params:
|
||||
all_names.difference_update(param.opts)
|
||||
all_names.difference_update(param.secondary_opts)
|
||||
return all_names
|
||||
|
||||
def get_help_option(self, ctx):
|
||||
"""Returns the help option object."""
|
||||
help_options = self.get_help_option_names(ctx)
|
||||
if not help_options:
|
||||
return
|
||||
|
||||
def show_help(ctx, param, value):
|
||||
if value and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help())
|
||||
ctx.exit()
|
||||
return Option(help_options, is_flag=True,
|
||||
is_eager=True, expose_value=False,
|
||||
callback=show_help,
|
||||
help='Show this message and exit.')
|
||||
|
||||
def make_parser(self, ctx):
|
||||
"""Creates the underlying option parser for this command."""
|
||||
parser = OptionParser(ctx)
|
||||
parser.allow_interspersed_args = ctx.allow_interspersed_args
|
||||
for param in self.get_params(ctx):
|
||||
param.add_to_parser(parser, ctx)
|
||||
return parser
|
||||
|
||||
def get_help(self, ctx):
|
||||
"""Formats the help into a string and returns it. This creates a
|
||||
formatter and will call into the following formatting methods:
|
||||
"""
|
||||
formatter = ctx.make_formatter()
|
||||
self.format_help(ctx, formatter)
|
||||
return formatter.getvalue().rstrip('\n')
|
||||
|
||||
def format_help(self, ctx, formatter):
|
||||
"""Writes the help into the formatter if it exists.
|
||||
|
||||
This calls into the following methods:
|
||||
|
||||
- :meth:`format_usage`
|
||||
- :meth:`format_help_text`
|
||||
- :meth:`format_options`
|
||||
- :meth:`format_epilog`
|
||||
"""
|
||||
self.format_usage(ctx, formatter)
|
||||
self.format_help_text(ctx, formatter)
|
||||
self.format_options(ctx, formatter)
|
||||
self.format_epilog(ctx, formatter)
|
||||
|
||||
def format_help_text(self, ctx, formatter):
|
||||
"""Writes the help text to the formatter if it exists."""
|
||||
if self.help:
|
||||
formatter.write_paragraph()
|
||||
with formatter.indentation():
|
||||
formatter.write_text(self.help)
|
||||
|
||||
def format_options(self, ctx, formatter):
|
||||
"""Writes all the options into the formatter if they exist."""
|
||||
opts = []
|
||||
for param in self.get_params(ctx):
|
||||
rv = param.get_help_record(ctx)
|
||||
if rv is not None:
|
||||
opts.append(rv)
|
||||
|
||||
if opts:
|
||||
with formatter.section('Options'):
|
||||
formatter.write_dl(opts)
|
||||
|
||||
def format_epilog(self, ctx, formatter):
|
||||
"""Writes the epilog into the formatter if it exists."""
|
||||
if self.epilog:
|
||||
formatter.write_paragraph()
|
||||
with formatter.indentation():
|
||||
formatter.write_text(self.epilog)
|
||||
|
||||
def parse_args(self, ctx, args):
|
||||
parser = self.make_parser(ctx)
|
||||
opts, args, param_order = parser.parse_args(args=args)
|
||||
|
||||
for param in iter_params_for_processing(
|
||||
param_order, self.get_params(ctx)):
|
||||
value, args = param.handle_parse_result(ctx, opts, args)
|
||||
|
||||
if args and not ctx.allow_extra_args and not ctx.resilient_parsing:
|
||||
ctx.fail('Got unexpected extra argument%s (%s)'
|
||||
% (len(args) != 1 and 's' or '',
|
||||
' '.join(map(make_str, args))))
|
||||
|
||||
ctx.args = args
|
||||
return args
|
||||
|
||||
def invoke(self, ctx):
|
||||
"""Given a context, this invokes the attached callback (if it exists)
|
||||
in the right way.
|
||||
"""
|
||||
if self.callback is not None:
|
||||
return ctx.invoke(self.callback, **ctx.params)
|
||||
|
||||
|
||||
class MultiCommand(Command):
|
||||
"""A multi command is the basic implementation of a command that
|
||||
dispatches to subcommands. The most common version is the
|
||||
:class:`Command`.
|
||||
|
||||
:param invoke_without_command: this controls how the multi command itself
|
||||
is invoked. By default it's only invoked
|
||||
if a subcommand is provided.
|
||||
:param no_args_is_help: this controls what happens if no arguments are
|
||||
provided. This option is enabled by default if
|
||||
`invoke_without_command` is disabled or disabled
|
||||
if it's enabled. If enabled this will add
|
||||
``--help`` as argument if no arguments are
|
||||
passed.
|
||||
:param subcommand_metavar: the string that is used in the documentation
|
||||
to indicate the subcommand place.
|
||||
:param chain: if this is set to `True` chaining of multiple subcommands
|
||||
is enabled. This restricts the form of commands in that
|
||||
they cannot have optional arguments but it allows
|
||||
multiple commands to be chained together.
|
||||
:param result_callback: the result callback to attach to this multi
|
||||
command.
|
||||
"""
|
||||
allow_extra_args = True
|
||||
allow_interspersed_args = False
|
||||
|
||||
def __init__(self, name=None, invoke_without_command=False,
|
||||
no_args_is_help=None, subcommand_metavar=None,
|
||||
chain=False, result_callback=None, **attrs):
|
||||
Command.__init__(self, name, **attrs)
|
||||
if no_args_is_help is None:
|
||||
no_args_is_help = not invoke_without_command
|
||||
self.no_args_is_help = no_args_is_help
|
||||
self.invoke_without_command = invoke_without_command
|
||||
if subcommand_metavar is None:
|
||||
if chain:
|
||||
subcommand_metavar = SUBCOMMANDS_METAVAR
|
||||
else:
|
||||
subcommand_metavar = SUBCOMMAND_METAVAR
|
||||
self.subcommand_metavar = subcommand_metavar
|
||||
self.chain = chain
|
||||
#: The result callback that is stored. This can be set or
|
||||
#: overridden with the :func:`resultcallback` decorator.
|
||||
self.result_callback = result_callback
|
||||
|
||||
def collect_usage_pieces(self, ctx):
|
||||
rv = Command.collect_usage_pieces(self, ctx)
|
||||
rv.append(self.subcommand_metavar)
|
||||
return rv
|
||||
|
||||
def format_options(self, ctx, formatter):
|
||||
Command.format_options(self, ctx, formatter)
|
||||
self.format_commands(ctx, formatter)
|
||||
|
||||
def resultcallback(self, replace=False):
|
||||
"""Adds a result callback to the chain command. By default if a
|
||||
result callback is already registered this will chain them but
|
||||
this can be disabled with the `replace` parameter. The result
|
||||
callback is invoked with the return value of the subcommand
|
||||
(or the list of return values from all subcommands if chaining
|
||||
is enabled) as well as the parameters as they would be passed
|
||||
to the main callback.
|
||||
|
||||
Example::
|
||||
|
||||
@click.group()
|
||||
@click.option('-i', '--input', default=23)
|
||||
def cli(input):
|
||||
return 42
|
||||
|
||||
@cli.resultcallback()
|
||||
def process_result(result, input):
|
||||
return result + input
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
:param replace: if set to `True` an already existing result
|
||||
callback will be removed.
|
||||
"""
|
||||
def decorator(f):
|
||||
old_callback = self.result_callback
|
||||
if old_callback is None or replace:
|
||||
self.result_callback = f
|
||||
return f
|
||||
def function(__value, *args, **kwargs):
|
||||
return f(old_callback(__value, *args, **kwargs),
|
||||
*args, **kwargs)
|
||||
self.result_callback = rv = update_wrapper(function, f)
|
||||
return rv
|
||||
return decorator
|
||||
|
||||
def format_commands(self, ctx, formatter):
|
||||
"""Extra format methods for multi methods that adds all the commands
|
||||
after the options.
|
||||
"""
|
||||
rows = []
|
||||
for subcommand in self.list_commands(ctx):
|
||||
cmd = self.get_command(ctx, subcommand)
|
||||
# What is this, the tool lied about a command. Ignore it
|
||||
if cmd is None:
|
||||
continue
|
||||
|
||||
help = cmd.short_help or ''
|
||||
rows.append((subcommand, help))
|
||||
|
||||
if rows:
|
||||
with formatter.section('Commands'):
|
||||
formatter.write_dl(rows)
|
||||
|
||||
def parse_args(self, ctx, args):
|
||||
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help())
|
||||
ctx.exit()
|
||||
return Command.parse_args(self, ctx, args)
|
||||
|
||||
def invoke(self, ctx):
|
||||
def _process_result(value):
|
||||
if self.result_callback is not None:
|
||||
value = ctx.invoke(self.result_callback, value,
|
||||
**ctx.params)
|
||||
return value
|
||||
|
||||
if not ctx.args:
|
||||
# If we are invoked without command the chain flag controls
|
||||
# how this happens. If we are not in chain mode, the return
|
||||
# value here is the return value of the command.
|
||||
# If however we are in chain mode, the return value is the
|
||||
# return value of the result processor invoked with an empty
|
||||
# list (which means that no subcommand actually was executed).
|
||||
if self.invoke_without_command:
|
||||
if not self.chain:
|
||||
return Command.invoke(self, ctx)
|
||||
with ctx:
|
||||
Command.invoke(self, ctx)
|
||||
return _process_result([])
|
||||
ctx.fail('Missing command.')
|
||||
|
||||
args = ctx.args
|
||||
|
||||
# If we're not in chain mode, we only allow the invocation of a
|
||||
# single command but we also inform the current context about the
|
||||
# name of the command to invoke.
|
||||
if not self.chain:
|
||||
# Make sure the context is entered so we do not clean up
|
||||
# resources until the result processor has worked.
|
||||
with ctx:
|
||||
cmd_name, cmd, args = self.resolve_command(ctx, args)
|
||||
ctx.invoked_subcommand = cmd_name
|
||||
Command.invoke(self, ctx)
|
||||
sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
|
||||
with sub_ctx:
|
||||
return _process_result(sub_ctx.command.invoke(sub_ctx))
|
||||
|
||||
# In chain mode we create the contexts step by step, but after the
|
||||
# base command has been invoked. Because at that point we do not
|
||||
# know the subcommands yet, the invoked subcommand attribute is
|
||||
# set to ``*`` to inform the command that subcommands are executed
|
||||
# but nothing else.
|
||||
with ctx:
|
||||
ctx.invoked_subcommand = args and '*' or None
|
||||
Command.invoke(self, ctx)
|
||||
|
||||
# Otherwise we make every single context and invoke them in a
|
||||
# chain. In that case the return value to the result processor
|
||||
# is the list of all invoked subcommand's results.
|
||||
contexts = []
|
||||
while args:
|
||||
cmd_name, cmd, args = self.resolve_command(ctx, args)
|
||||
sub_ctx = cmd.make_context(cmd_name, args, parent=ctx,
|
||||
allow_extra_args=True,
|
||||
allow_interspersed_args=False)
|
||||
contexts.append(sub_ctx)
|
||||
args = sub_ctx.args
|
||||
|
||||
rv = []
|
||||
for sub_ctx in contexts:
|
||||
with sub_ctx:
|
||||
rv.append(sub_ctx.command.invoke(sub_ctx))
|
||||
return _process_result(rv)
|
||||
|
||||
def resolve_command(self, ctx, args):
|
||||
cmd_name = make_str(args[0])
|
||||
original_cmd_name = cmd_name
|
||||
|
||||
# Get the command
|
||||
cmd = self.get_command(ctx, cmd_name)
|
||||
|
||||
# If we can't find the command but there is a normalization
|
||||
# function available, we try with that one.
|
||||
if cmd is None and ctx.token_normalize_func is not None:
|
||||
cmd_name = ctx.token_normalize_func(cmd_name)
|
||||
cmd = self.get_command(ctx, cmd_name)
|
||||
|
||||
# If we don't find the command we want to show an error message
|
||||
# to the user that it was not provided. However, there is
|
||||
# something else we should do: if the first argument looks like
|
||||
# an option we want to kick off parsing again for arguments to
|
||||
# resolve things like --help which now should go to the main
|
||||
# place.
|
||||
if cmd is None:
|
||||
if split_opt(cmd_name)[0]:
|
||||
self.parse_args(ctx, ctx.args)
|
||||
ctx.fail('No such command "%s".' % original_cmd_name)
|
||||
|
||||
return cmd_name, cmd, args[1:]
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
"""Given a context and a command name, this returns a
|
||||
:class:`Command` object if it exists or returns `None`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def list_commands(self, ctx):
|
||||
"""Returns a list of subcommand names in the order they should
|
||||
appear.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class Group(MultiCommand):
|
||||
"""A group allows a command to have subcommands attached. This is the
|
||||
most common way to implement nesting in Click.
|
||||
|
||||
:param commands: a dictionary of commands.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, commands=None, **attrs):
|
||||
MultiCommand.__init__(self, name, **attrs)
|
||||
#: the registered subcommands by their exported names.
|
||||
self.commands = commands or {}
|
||||
|
||||
def add_command(self, cmd, name=None):
|
||||
"""Registers another :class:`Command` with this group. If the name
|
||||
is not provided, the name of the command is used.
|
||||
"""
|
||||
name = name or cmd.name
|
||||
if name is None:
|
||||
raise TypeError('Command has no name.')
|
||||
self.commands[name] = cmd
|
||||
|
||||
def command(self, *args, **kwargs):
|
||||
"""A shortcut decorator for declaring and attaching a command to
|
||||
the group. This takes the same arguments as :func:`command` but
|
||||
immediately registers the created command with this instance by
|
||||
calling into :meth:`add_command`.
|
||||
"""
|
||||
def decorator(f):
|
||||
cmd = command(*args, **kwargs)(f)
|
||||
self.add_command(cmd)
|
||||
return cmd
|
||||
return decorator
|
||||
|
||||
def group(self, *args, **kwargs):
|
||||
"""A shortcut decorator for declaring and attaching a group to
|
||||
the group. This takes the same arguments as :func:`group` but
|
||||
immediately registers the created command with this instance by
|
||||
calling into :meth:`add_command`.
|
||||
"""
|
||||
def decorator(f):
|
||||
cmd = group(*args, **kwargs)(f)
|
||||
self.add_command(cmd)
|
||||
return cmd
|
||||
return decorator
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
return self.commands.get(cmd_name)
|
||||
|
||||
def list_commands(self, ctx):
|
||||
return sorted(self.commands)
|
||||
|
||||
|
||||
class CommandCollection(MultiCommand):
|
||||
"""A command collection is a multi command that merges multiple multi
|
||||
commands together into one. This is a straightforward implementation
|
||||
that accepts a list of different multi commands as sources and
|
||||
provides all the commands for each of them.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, sources=None, **attrs):
|
||||
MultiCommand.__init__(self, name, **attrs)
|
||||
#: The list of registered multi commands.
|
||||
self.sources = sources or []
|
||||
|
||||
def add_source(self, multi_cmd):
|
||||
"""Adds a new multi command to the chain dispatcher."""
|
||||
self.sources.append(multi_cmd)
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
for source in self.sources:
|
||||
rv = source.get_command(ctx, cmd_name)
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
def list_commands(self, ctx):
|
||||
rv = set()
|
||||
for source in self.sources:
|
||||
rv.update(source.list_commands(ctx))
|
||||
return sorted(rv)
|
||||
|
||||
|
||||
class Parameter(object):
|
||||
"""A parameter to a command comes in two versions: they are either
|
||||
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
|
||||
not supported by design as some of the internals for parsing are
|
||||
intentionally not finalized.
|
||||
|
||||
Some settings are supported by both options and arguments.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Changed signature for parameter callback to also be passed the
|
||||
parameter. In Click 2.0, the old callback format will still work,
|
||||
but it will raise a warning to give you change to migrate the
|
||||
code easier.
|
||||
|
||||
:param param_decls: the parameter declarations for this option or
|
||||
argument. This is a list of flags or argument
|
||||
names.
|
||||
:param type: the type that should be used. Either a :class:`ParamType`
|
||||
or a Python type. The later is converted into the former
|
||||
automatically if supported.
|
||||
:param required: controls if this is optional or not.
|
||||
:param default: the default value if omitted. This can also be a callable,
|
||||
in which case it's invoked when the default is needed
|
||||
without any arguments.
|
||||
:param callback: a callback that should be executed after the parameter
|
||||
was matched. This is called as ``fn(ctx, param,
|
||||
value)`` and needs to return the value. Before Click
|
||||
2.0, the signature was ``(ctx, value)``.
|
||||
:param nargs: the number of arguments to match. If not ``1`` the return
|
||||
value is a tuple instead of single value.
|
||||
:param metavar: how the value is represented in the help page.
|
||||
:param expose_value: if this is `True` then the value is passed onwards
|
||||
to the command callback and stored on the context,
|
||||
otherwise it's skipped.
|
||||
:param is_eager: eager values are processed before non eager ones. This
|
||||
should not be set for arguments or it will inverse the
|
||||
order of processing.
|
||||
:param envvar: a string or list of strings that are environment variables
|
||||
that should be checked.
|
||||
"""
|
||||
param_type_name = 'parameter'
|
||||
|
||||
def __init__(self, param_decls=None, type=None, required=False,
|
||||
default=None, callback=None, nargs=1, metavar=None,
|
||||
expose_value=True, is_eager=False, envvar=None):
|
||||
self.name, self.opts, self.secondary_opts = \
|
||||
self._parse_decls(param_decls or (), expose_value)
|
||||
self.type = convert_type(type, default)
|
||||
self.required = required
|
||||
self.callback = callback
|
||||
self.nargs = nargs
|
||||
self.multiple = False
|
||||
self.expose_value = expose_value
|
||||
self.default = default
|
||||
self.is_eager = is_eager
|
||||
self.metavar = metavar
|
||||
self.envvar = envvar
|
||||
|
||||
def make_metavar(self):
|
||||
if self.metavar is not None:
|
||||
return self.metavar
|
||||
metavar = self.type.get_metavar(self)
|
||||
if metavar is None:
|
||||
metavar = self.type.name.upper()
|
||||
if self.nargs != 1:
|
||||
metavar += '...'
|
||||
return metavar
|
||||
|
||||
def get_default(self, ctx):
|
||||
"""Given a context variable this calculates the default value."""
|
||||
# Otherwise go with the regular default.
|
||||
if callable(self.default):
|
||||
rv = self.default()
|
||||
else:
|
||||
rv = self.default
|
||||
return self.type_cast_value(ctx, rv)
|
||||
|
||||
def add_to_parser(self, parser, ctx):
|
||||
pass
|
||||
|
||||
def consume_value(self, ctx, opts):
|
||||
value = opts.get(self.name)
|
||||
if value is None:
|
||||
value = ctx.lookup_default(self.name)
|
||||
if value is None:
|
||||
value = self.value_from_envvar(ctx)
|
||||
return value
|
||||
|
||||
def type_cast_value(self, ctx, value):
|
||||
"""Given a value this runs it properly through the type system.
|
||||
This automatically handles things like `nargs` and `multiple`.
|
||||
"""
|
||||
def _convert(value, level):
|
||||
if level == 0:
|
||||
return self.type(value, self, ctx)
|
||||
return tuple(_convert(x, level - 1) for x in value or ())
|
||||
return _convert(value, (self.nargs != 1) + bool(self.multiple))
|
||||
|
||||
def process_value(self, ctx, value):
|
||||
"""Given a value and context this runs the logic to convert the
|
||||
value as necessary.
|
||||
"""
|
||||
# If the value we were given is None we do nothing. This way
|
||||
# code that calls this can easily figure out if something was
|
||||
# not provided. Otherwise it would be converted into an empty
|
||||
# tuple for multiple invocations which is inconvenient.
|
||||
if value is not None:
|
||||
return self.type_cast_value(ctx, value)
|
||||
|
||||
def value_is_missing(self, value):
|
||||
if value is None:
|
||||
return True
|
||||
if (self.nargs != 1 or self.multiple) and value == ():
|
||||
return True
|
||||
return False
|
||||
|
||||
def full_process_value(self, ctx, value):
|
||||
value = self.process_value(ctx, value)
|
||||
|
||||
if value is None:
|
||||
value = self.get_default(ctx)
|
||||
|
||||
if self.required and self.value_is_missing(value):
|
||||
ctx.fail(self.get_missing_message(ctx))
|
||||
|
||||
return value
|
||||
|
||||
def get_missing_message(self, ctx):
|
||||
rv = 'Missing %s %s.' % (
|
||||
self.param_type_name,
|
||||
' / '.join('"%s"' % x for x in chain(
|
||||
self.opts, self.secondary_opts)),
|
||||
)
|
||||
extra = self.type.get_missing_message(self)
|
||||
if extra:
|
||||
rv += ' ' + extra
|
||||
return rv
|
||||
|
||||
def resolve_envvar_value(self, ctx):
|
||||
if self.envvar is None:
|
||||
return
|
||||
if isinstance(self.envvar, (tuple, list)):
|
||||
for envvar in self.envvar:
|
||||
rv = os.environ.get(envvar)
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
return os.environ.get(self.envvar)
|
||||
|
||||
def value_from_envvar(self, ctx):
|
||||
rv = self.resolve_envvar_value(ctx)
|
||||
if rv is not None and self.nargs != 1:
|
||||
rv = self.type.split_envvar_value(rv)
|
||||
return rv
|
||||
|
||||
def handle_parse_result(self, ctx, opts, args):
|
||||
with augment_usage_errors(ctx, param=self):
|
||||
value = self.consume_value(ctx, opts)
|
||||
try:
|
||||
value = self.full_process_value(ctx, value)
|
||||
except Exception:
|
||||
if not ctx.resilient_parsing:
|
||||
raise
|
||||
value = None
|
||||
if self.callback is not None:
|
||||
try:
|
||||
value = invoke_param_callback(
|
||||
self.callback, ctx, self, value)
|
||||
except Exception:
|
||||
if not ctx.resilient_parsing:
|
||||
raise
|
||||
|
||||
if self.expose_value:
|
||||
ctx.params[self.name] = value
|
||||
return value, args
|
||||
|
||||
def get_help_record(self, ctx):
|
||||
pass
|
||||
|
||||
def get_usage_pieces(self, ctx):
|
||||
return []
|
||||
|
||||
|
||||
class Option(Parameter):
|
||||
"""Options are usually optional values on the command line and
|
||||
have some extra features that arguments don't have.
|
||||
|
||||
All other parameters are passed onwards to the parameter constructor.
|
||||
|
||||
:param show_default: controls if the default value should be shown on the
|
||||
help page. Normally, defaults are not shown.
|
||||
:param prompt: if set to `True` or a non empty string then the user will
|
||||
be prompted for input if not set. If set to `True` the
|
||||
prompt will be the option name capitalized.
|
||||
:param confirmation_prompt: if set then the value will need to be confirmed
|
||||
if it was prompted for.
|
||||
:param hide_input: if this is `True` then the input on the prompt will be
|
||||
hidden from the user. This is useful for password
|
||||
input.
|
||||
:param is_flag: forces this option to act as a flag. The default is
|
||||
auto detection.
|
||||
:param flag_value: which value should be used for this flag if it's
|
||||
enabled. This is set to a boolean automatically if
|
||||
the option string contains a slash to mark two options.
|
||||
:param multiple: if this is set to `True` then the argument is accepted
|
||||
multiple times and recorded. This is similar to ``nargs``
|
||||
in how it works but supports arbitrary number of
|
||||
arguments.
|
||||
:param count: this flag makes an option increment an integer.
|
||||
:param allow_from_autoenv: if this is enabled then the value of this
|
||||
parameter will be pulled from an environment
|
||||
variable in case a prefix is defined on the
|
||||
context.
|
||||
:param help: the help string.
|
||||
"""
|
||||
param_type_name = 'option'
|
||||
|
||||
def __init__(self, param_decls=None, show_default=False,
|
||||
prompt=False, confirmation_prompt=False,
|
||||
hide_input=False, is_flag=None, flag_value=None,
|
||||
multiple=False, count=False, allow_from_autoenv=True,
|
||||
type=None, help=None, **attrs):
|
||||
default_is_missing = attrs.get('default', _missing) is _missing
|
||||
Parameter.__init__(self, param_decls, type=type, **attrs)
|
||||
|
||||
if prompt is True:
|
||||
prompt_text = self.name.replace('_', ' ').capitalize()
|
||||
elif prompt is False:
|
||||
prompt_text = None
|
||||
else:
|
||||
prompt_text = prompt
|
||||
self.prompt = prompt_text
|
||||
self.confirmation_prompt = confirmation_prompt
|
||||
self.hide_input = hide_input
|
||||
|
||||
# Flags
|
||||
if is_flag is None:
|
||||
if flag_value is not None:
|
||||
is_flag = True
|
||||
else:
|
||||
is_flag = bool(self.secondary_opts)
|
||||
if is_flag and default_is_missing:
|
||||
self.default = False
|
||||
if flag_value is None:
|
||||
flag_value = not self.default
|
||||
self.is_flag = is_flag
|
||||
self.flag_value = flag_value
|
||||
if self.is_flag and isinstance(self.flag_value, bool) \
|
||||
and type is None:
|
||||
self.type = BOOL
|
||||
self.is_bool_flag = True
|
||||
else:
|
||||
self.is_bool_flag = False
|
||||
|
||||
# Counting
|
||||
self.count = count
|
||||
if count:
|
||||
if type is None:
|
||||
self.type = IntRange(min=0)
|
||||
if default_is_missing:
|
||||
self.default = 0
|
||||
|
||||
self.multiple = multiple
|
||||
self.allow_from_autoenv = allow_from_autoenv
|
||||
self.help = help
|
||||
self.show_default = show_default
|
||||
|
||||
# Sanity check for stuff we don't support
|
||||
if __debug__:
|
||||
if self.prompt and self.is_flag and not self.is_bool_flag:
|
||||
raise TypeError('Cannot prompt for flags that are not bools.')
|
||||
if not self.is_bool_flag and self.secondary_opts:
|
||||
raise TypeError('Got secondary option for non boolean flag.')
|
||||
if self.is_bool_flag and self.hide_input \
|
||||
and self.prompt is not None:
|
||||
raise TypeError('Hidden input does not work with boolean '
|
||||
'flag prompts.')
|
||||
if self.count:
|
||||
if self.multiple:
|
||||
raise TypeError('Options cannot be multiple and count '
|
||||
'at the same time.')
|
||||
elif self.is_flag:
|
||||
raise TypeError('Options cannot be count and flags at '
|
||||
'the same time.')
|
||||
|
||||
def _parse_decls(self, decls, expose_value):
|
||||
opts = []
|
||||
secondary_opts = []
|
||||
name = None
|
||||
possible_names = []
|
||||
|
||||
for decl in decls:
|
||||
if isidentifier(decl):
|
||||
if name is not None:
|
||||
raise TypeError('Name defined twice')
|
||||
name = decl
|
||||
else:
|
||||
split_char = decl[:1] == '/' and ';' or '/'
|
||||
if split_char in decl:
|
||||
first, second = decl.split(split_char, 1)
|
||||
first = first.rstrip()
|
||||
possible_names.append(split_opt(first))
|
||||
opts.append(first)
|
||||
secondary_opts.append(second.lstrip())
|
||||
else:
|
||||
possible_names.append(split_opt(decl))
|
||||
opts.append(decl)
|
||||
|
||||
if name is None and possible_names:
|
||||
possible_names.sort(key=lambda x: len(x[0]))
|
||||
name = possible_names[-1][1].replace('-', '_').lower()
|
||||
if not isidentifier(name):
|
||||
name = None
|
||||
|
||||
if name is None:
|
||||
if not expose_value:
|
||||
return None, opts, secondary_opts
|
||||
raise TypeError('Could not determine name for option')
|
||||
|
||||
if not opts and not secondary_opts:
|
||||
raise TypeError('No options defined but a name was passed (%s). '
|
||||
'Did you mean to declare an argument instead '
|
||||
'of an option?' % name)
|
||||
|
||||
return name, opts, secondary_opts
|
||||
|
||||
def add_to_parser(self, parser, ctx):
|
||||
kwargs = {
|
||||
'dest': self.name,
|
||||
'nargs': self.nargs,
|
||||
'obj': self,
|
||||
}
|
||||
|
||||
if self.multiple:
|
||||
action = 'append'
|
||||
elif self.count:
|
||||
action = 'count'
|
||||
else:
|
||||
action = 'store'
|
||||
|
||||
if self.is_flag:
|
||||
kwargs.pop('nargs', None)
|
||||
if self.is_bool_flag and self.secondary_opts:
|
||||
parser.add_option(self.opts, action=action + '_const',
|
||||
const=True, **kwargs)
|
||||
parser.add_option(self.secondary_opts, action=action +
|
||||
'_const', const=False, **kwargs)
|
||||
else:
|
||||
parser.add_option(self.opts, action=action + '_const',
|
||||
const=self.flag_value,
|
||||
**kwargs)
|
||||
else:
|
||||
kwargs['action'] = action
|
||||
parser.add_option(self.opts, **kwargs)
|
||||
|
||||
def get_help_record(self, ctx):
|
||||
any_prefix_is_slash = []
|
||||
|
||||
def _write_opts(opts):
|
||||
rv, any_slashes = join_options(opts)
|
||||
if any_slashes:
|
||||
any_prefix_is_slash[:] = [True]
|
||||
if not self.is_flag and not self.count:
|
||||
rv += ' ' + self.make_metavar()
|
||||
return rv
|
||||
|
||||
rv = [_write_opts(self.opts)]
|
||||
if self.secondary_opts:
|
||||
rv.append(_write_opts(self.secondary_opts))
|
||||
|
||||
help = self.help or ''
|
||||
extra = []
|
||||
if self.default is not None and self.show_default:
|
||||
extra.append('default: %s' % self.default)
|
||||
if self.required:
|
||||
extra.append('required')
|
||||
if extra:
|
||||
help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra))
|
||||
|
||||
return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help)
|
||||
|
||||
def get_default(self, ctx):
|
||||
# If we're a non boolean flag out default is more complex because
|
||||
# we need to look at all flags in the same group to figure out
|
||||
# if we're the the default one in which case we return the flag
|
||||
# value as default.
|
||||
if self.is_flag and not self.is_bool_flag:
|
||||
for param in ctx.command.params:
|
||||
if param.name == self.name and param.default:
|
||||
return param.flag_value
|
||||
return None
|
||||
return Parameter.get_default(self, ctx)
|
||||
|
||||
def prompt_for_value(self, ctx):
|
||||
"""This is an alternative flow that can be activated in the full
|
||||
value processing if a value does not exist. It will prompt the
|
||||
user until a valid value exists and then returns the processed
|
||||
value as result.
|
||||
"""
|
||||
# Calculate the default before prompting anything to be stable.
|
||||
default = self.get_default(ctx)
|
||||
|
||||
# If this is a prompt for a flag we need to handle this
|
||||
# differently.
|
||||
if self.is_bool_flag:
|
||||
return confirm(self.prompt, default)
|
||||
|
||||
return prompt(self.prompt, default=default,
|
||||
hide_input=self.hide_input,
|
||||
confirmation_prompt=self.confirmation_prompt,
|
||||
value_proc=lambda x: self.process_value(ctx, x))
|
||||
|
||||
def resolve_envvar_value(self, ctx):
|
||||
rv = Parameter.resolve_envvar_value(self, ctx)
|
||||
if rv is not None:
|
||||
return rv
|
||||
if self.allow_from_autoenv and \
|
||||
ctx.auto_envvar_prefix is not None:
|
||||
envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper())
|
||||
return os.environ.get(envvar)
|
||||
|
||||
def value_from_envvar(self, ctx):
|
||||
rv = self.resolve_envvar_value(ctx)
|
||||
if rv is None:
|
||||
return None
|
||||
value_depth = (self.nargs != 1) + bool(self.multiple)
|
||||
if value_depth > 0 and rv is not None:
|
||||
rv = self.type.split_envvar_value(rv)
|
||||
if self.multiple and self.nargs != 1:
|
||||
rv = batch(rv, self.nargs)
|
||||
return rv
|
||||
|
||||
def full_process_value(self, ctx, value):
|
||||
if value is None and self.prompt is not None \
|
||||
and not ctx.resilient_parsing:
|
||||
return self.prompt_for_value(ctx)
|
||||
return Parameter.full_process_value(self, ctx, value)
|
||||
|
||||
|
||||
class Argument(Parameter):
|
||||
"""Arguments are positional parameters to a command. They generally
|
||||
provide fewer features than options but can have infinite ``nargs``
|
||||
and are required by default.
|
||||
|
||||
All parameters are passed onwards to the parameter constructor.
|
||||
"""
|
||||
param_type_name = 'argument'
|
||||
|
||||
def __init__(self, param_decls, required=None, **attrs):
|
||||
if required is None:
|
||||
if attrs.get('default') is not None:
|
||||
required = False
|
||||
else:
|
||||
required = attrs.get('nargs', 1) > 0
|
||||
Parameter.__init__(self, param_decls, required=required, **attrs)
|
||||
|
||||
def make_metavar(self):
|
||||
if self.metavar is not None:
|
||||
return self.metavar
|
||||
var = self.name.upper()
|
||||
if not self.required:
|
||||
var = '[%s]' % var
|
||||
if self.nargs != 1:
|
||||
var += '...'
|
||||
return var
|
||||
|
||||
def _parse_decls(self, decls, expose_value):
|
||||
if not decls:
|
||||
if not expose_value:
|
||||
return None, [], []
|
||||
raise TypeError('Could not determine name for argument')
|
||||
if len(decls) == 1:
|
||||
name = arg = decls[0]
|
||||
name = name.replace('-', '_').lower()
|
||||
elif len(decls) == 2:
|
||||
name, arg = decls
|
||||
else:
|
||||
raise TypeError('Arguments take exactly one or two '
|
||||
'parameter declarations, got %d' % len(decls))
|
||||
return name, [arg], []
|
||||
|
||||
def get_usage_pieces(self, ctx):
|
||||
return [self.make_metavar()]
|
||||
|
||||
def add_to_parser(self, parser, ctx):
|
||||
parser.add_argument(dest=self.name, nargs=self.nargs,
|
||||
obj=self)
|
||||
|
||||
|
||||
# Circular dependency between decorators and core
|
||||
from .decorators import command, group
|
293
click/decorators.py
Normal file
|
@ -0,0 +1,293 @@
|
|||
import sys
|
||||
import inspect
|
||||
|
||||
from functools import update_wrapper
|
||||
|
||||
from ._compat import iteritems
|
||||
from .utils import echo
|
||||
|
||||
|
||||
def pass_context(f):
|
||||
"""Marks a callback as wanting to receive the current context
|
||||
object as first argument.
|
||||
"""
|
||||
f.__click_pass_context__ = True
|
||||
return f
|
||||
|
||||
|
||||
def pass_obj(f):
|
||||
"""Similar to :func:`pass_context`, but only pass the object on the
|
||||
context onwards (:attr:`Context.obj`). This is useful if that object
|
||||
represents the state of a nested system.
|
||||
"""
|
||||
@pass_context
|
||||
def new_func(*args, **kwargs):
|
||||
ctx = args[0]
|
||||
return ctx.invoke(f, ctx.obj, *args[1:], **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def make_pass_decorator(object_type, ensure=False):
|
||||
"""Given an object type this creates a decorator that will work
|
||||
similar to :func:`pass_obj` but instead of passing the object of the
|
||||
current context, it will find the innermost context of type
|
||||
:func:`object_type`.
|
||||
|
||||
This generates a decorator that works roughly like this::
|
||||
|
||||
from functools import update_wrapper
|
||||
|
||||
def decorator(f):
|
||||
@pass_context
|
||||
def new_func(ctx, *args, **kwargs):
|
||||
obj = ctx.find_object(object_type)
|
||||
return ctx.invoke(f, obj, *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
return decorator
|
||||
|
||||
:param object_type: the type of the object to pass.
|
||||
:param ensure: if set to `True`, a new object will be created and
|
||||
remembered on the context if it's not there yet.
|
||||
"""
|
||||
def decorator(f):
|
||||
@pass_context
|
||||
def new_func(*args, **kwargs):
|
||||
ctx = args[0]
|
||||
if ensure:
|
||||
obj = ctx.ensure_object(object_type)
|
||||
else:
|
||||
obj = ctx.find_object(object_type)
|
||||
if obj is None:
|
||||
raise RuntimeError('Managed to invoke callback without a '
|
||||
'context object of type %r existing'
|
||||
% object_type.__name__)
|
||||
return ctx.invoke(f, obj, *args[1:], **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
return decorator
|
||||
|
||||
|
||||
def _make_command(f, name, attrs, cls):
|
||||
if isinstance(f, Command):
|
||||
raise TypeError('Attempted to convert a callback into a '
|
||||
'command twice.')
|
||||
try:
|
||||
params = f.__click_params__
|
||||
params.reverse()
|
||||
del f.__click_params__
|
||||
except AttributeError:
|
||||
params = []
|
||||
help = attrs.get('help')
|
||||
if help is None:
|
||||
help = inspect.getdoc(f)
|
||||
if isinstance(help, bytes):
|
||||
help = help.decode('utf-8')
|
||||
else:
|
||||
help = inspect.cleandoc(help)
|
||||
attrs['help'] = help
|
||||
return cls(name=name or f.__name__.lower(),
|
||||
callback=f, params=params, **attrs)
|
||||
|
||||
|
||||
def command(name=None, cls=None, **attrs):
|
||||
"""Creates a new :class:`Command` and uses the decorated function as
|
||||
callback. This will also automatically attach all decorated
|
||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
||||
|
||||
The name of the command defaults to the name of the function. If you
|
||||
want to change that, you can pass the intended name as the first
|
||||
argument.
|
||||
|
||||
All keyword arguments are forwarded to the underlying command class.
|
||||
|
||||
Once decorated the function turns into a :class:`Command` instance
|
||||
that can be invoked as a command line utility or be attached to a
|
||||
command :class:`Group`.
|
||||
|
||||
:param name: the name of the command. This defaults to the function
|
||||
name.
|
||||
:param cls: the command class to instantiate. This defaults to
|
||||
:class:`Command`.
|
||||
"""
|
||||
if cls is None:
|
||||
cls = Command
|
||||
def decorator(f):
|
||||
return _make_command(f, name, attrs, cls)
|
||||
return decorator
|
||||
|
||||
|
||||
def group(name=None, **attrs):
|
||||
"""Creates a new :class:`Group` with a function as callback. This
|
||||
works otherwise the same as :func:`command` just that the `cls`
|
||||
parameter is set to :class:`Group`.
|
||||
"""
|
||||
attrs.setdefault('cls', Group)
|
||||
return command(name, **attrs)
|
||||
|
||||
|
||||
def _param_memo(f, param):
|
||||
if isinstance(f, Command):
|
||||
f.params.append(param)
|
||||
else:
|
||||
if not hasattr(f, '__click_params__'):
|
||||
f.__click_params__ = []
|
||||
f.__click_params__.append(param)
|
||||
|
||||
|
||||
def argument(*param_decls, **attrs):
|
||||
"""Attaches an option to the command. All positional arguments are
|
||||
passed as parameter declarations to :class:`Argument`; all keyword
|
||||
arguments are forwarded unchanged. This is equivalent to creating an
|
||||
:class:`Option` instance manually and attaching it to the
|
||||
:attr:`Command.params` list.
|
||||
"""
|
||||
def decorator(f):
|
||||
_param_memo(f, Argument(param_decls, **attrs))
|
||||
return f
|
||||
return decorator
|
||||
|
||||
|
||||
def option(*param_decls, **attrs):
|
||||
"""Attaches an option to the command. All positional arguments are
|
||||
passed as parameter declarations to :class:`Option`; all keyword
|
||||
arguments are forwarded unchanged. This is equivalent to creating an
|
||||
:class:`Option` instance manually and attaching it to the
|
||||
:attr:`Command.params` list.
|
||||
"""
|
||||
def decorator(f):
|
||||
if 'help' in attrs:
|
||||
attrs['help'] = inspect.cleandoc(attrs['help'])
|
||||
_param_memo(f, Option(param_decls, **attrs))
|
||||
return f
|
||||
return decorator
|
||||
|
||||
|
||||
def confirmation_option(*param_decls, **attrs):
|
||||
"""Shortcut for confirmation prompts that can be ignored by passing
|
||||
``--yes`` as parameter.
|
||||
|
||||
This is equivalent to decorating a function with :func:`option` with
|
||||
the following parameters::
|
||||
|
||||
def callback(ctx, param, value):
|
||||
if not value:
|
||||
ctx.abort()
|
||||
|
||||
@click.command()
|
||||
@click.option('--yes', is_flag=True, callback=callback,
|
||||
expose_value=False, prompt='Do you want to continue?')
|
||||
def dropdb():
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
def callback(ctx, param, value):
|
||||
if not value:
|
||||
ctx.abort()
|
||||
attrs.setdefault('is_flag', True)
|
||||
attrs.setdefault('callback', callback)
|
||||
attrs.setdefault('expose_value', False)
|
||||
attrs.setdefault('prompt', 'Do you want to continue?')
|
||||
attrs.setdefault('help', 'Confirm the action without prompting.')
|
||||
return option(*(param_decls or ('--yes',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
def password_option(*param_decls, **attrs):
|
||||
"""Shortcut for password prompts.
|
||||
|
||||
This is equivalent to decorating a function with :func:`option` with
|
||||
the following parameters::
|
||||
|
||||
@click.command()
|
||||
@click.option('--password', prompt=True, confirmation_prompt=True,
|
||||
hide_input=True)
|
||||
def changeadmin(password):
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
attrs.setdefault('prompt', True)
|
||||
attrs.setdefault('confirmation_prompt', True)
|
||||
attrs.setdefault('hide_input', True)
|
||||
return option(*(param_decls or ('--password',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
def version_option(version=None, *param_decls, **attrs):
|
||||
"""Adds a ``--version`` option which immediately ends the program
|
||||
printing out the version number. This is implemented as an eager
|
||||
option that prints the version and exits the program in the callback.
|
||||
|
||||
:param version: the version number to show. If not provided Click
|
||||
attempts an auto discovery via setuptools.
|
||||
:param prog_name: the name of the program (defaults to autodetection)
|
||||
:param message: custom message to show instead of the default
|
||||
(``'%(prog)s, version %(version)s'``)
|
||||
:param others: everything else is forwarded to :func:`option`.
|
||||
"""
|
||||
if version is None:
|
||||
module = sys._getframe(1).f_globals.get('__name__')
|
||||
def decorator(f):
|
||||
prog_name = attrs.pop('prog_name', None)
|
||||
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
||||
|
||||
def callback(ctx, param, value):
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
prog = prog_name
|
||||
if prog is None:
|
||||
prog = ctx.find_root().info_name
|
||||
ver = version
|
||||
if ver is None:
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
for dist in pkg_resources.working_set:
|
||||
scripts = dist.get_entry_map().get('console_scripts') or {}
|
||||
for script_name, entry_point in iteritems(scripts):
|
||||
if entry_point.module_name == module:
|
||||
ver = dist.version
|
||||
break
|
||||
if ver is None:
|
||||
raise RuntimeError('Could not determine version')
|
||||
echo(message % {
|
||||
'prog': prog,
|
||||
'version': ver,
|
||||
})
|
||||
ctx.exit()
|
||||
|
||||
attrs.setdefault('is_flag', True)
|
||||
attrs.setdefault('expose_value', False)
|
||||
attrs.setdefault('is_eager', True)
|
||||
attrs.setdefault('help', 'Show the version and exit.')
|
||||
attrs['callback'] = callback
|
||||
return option(*(param_decls or ('--version',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
def help_option(*param_decls, **attrs):
|
||||
"""Adds a ``--help`` option which immediately ends the program
|
||||
printing out the help page. This is usually unnecessary to add as
|
||||
this is added by default to all commands unless suppressed.
|
||||
|
||||
Like :func:`version_option`, this is implemented as eager option that
|
||||
prints in the callback and exits.
|
||||
|
||||
All arguments are forwarded to :func:`option`.
|
||||
"""
|
||||
def decorator(f):
|
||||
def callback(ctx, param, value):
|
||||
if value and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help())
|
||||
ctx.exit()
|
||||
attrs.setdefault('is_flag', True)
|
||||
attrs.setdefault('expose_value', False)
|
||||
attrs.setdefault('help', 'Show this message and exit.')
|
||||
attrs.setdefault('is_eager', True)
|
||||
attrs['callback'] = callback
|
||||
return option(*(param_decls or ('--help',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
# Circular dependencies between core and decorators
|
||||
from .core import Command, Group, Argument, Option
|
101
click/exceptions.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
from ._compat import PY2, filename_to_ui, get_text_stderr
|
||||
from .utils import echo
|
||||
|
||||
|
||||
class ClickException(Exception):
|
||||
"""An exception that Click can handle and show to the user."""
|
||||
|
||||
#: The exit code for this exception
|
||||
exit_code = 1
|
||||
|
||||
def __init__(self, message):
|
||||
if PY2:
|
||||
Exception.__init__(self, message.encode('utf-8'))
|
||||
else:
|
||||
Exception.__init__(self, message)
|
||||
self.message = message
|
||||
|
||||
def format_message(self):
|
||||
return self.message
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
echo('Error: %s' % self.format_message(), file=file)
|
||||
|
||||
|
||||
class UsageError(ClickException):
|
||||
"""An internal exception that signals a usage error. This typically
|
||||
aborts any further handling.
|
||||
|
||||
:param message: the error message to display.
|
||||
:param ctx: optionally the context that caused this error. Click will
|
||||
fill in the context automatically in some situations.
|
||||
"""
|
||||
exit_code = 2
|
||||
|
||||
def __init__(self, message, ctx=None):
|
||||
ClickException.__init__(self, message)
|
||||
self.ctx = ctx
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
if self.ctx is not None:
|
||||
echo(self.ctx.get_usage() + '\n', file=file)
|
||||
echo('Error: %s' % self.format_message(), file=file)
|
||||
|
||||
|
||||
class BadParameter(UsageError):
|
||||
"""An exception that formats out a standardized error message for a
|
||||
bad parameter. This is useful when thrown from a callback or type as
|
||||
Click will attach contextual information to it (for instance, which
|
||||
parameter it is).
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param param: the parameter object that caused this error. This can
|
||||
be left out, and Click will attach this info itself
|
||||
if possible.
|
||||
:param param_hint: a string that shows up as parameter name. This
|
||||
can be used as alternative to `param` in cases
|
||||
where custom validation should happen. If it is
|
||||
a string it's used as such, if it's a list then
|
||||
each item is quoted and separated.
|
||||
"""
|
||||
|
||||
def __init__(self, message, ctx=None, param=None,
|
||||
param_hint=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
self.param = param
|
||||
self.param_hint = param_hint
|
||||
|
||||
def format_message(self):
|
||||
if self.param_hint is not None:
|
||||
param_hint = self.param_hint
|
||||
elif self.param is not None:
|
||||
param_hint = self.param.opts or [self.param.name]
|
||||
else:
|
||||
return 'Invalid value: %s' % self.message
|
||||
if isinstance(param_hint, (tuple, list)):
|
||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
||||
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
||||
|
||||
|
||||
class FileError(ClickException):
|
||||
"""Raised if a file cannot be opened."""
|
||||
|
||||
def __init__(self, filename, hint=None):
|
||||
ui_filename = filename_to_ui(filename)
|
||||
if hint is None:
|
||||
hint = 'unknown error'
|
||||
ClickException.__init__(self, hint)
|
||||
self.ui_filename = ui_filename
|
||||
self.filename = filename
|
||||
|
||||
def format_message(self):
|
||||
return 'Could not open file %s: %s' % (self.ui_filename, self.message)
|
||||
|
||||
|
||||
class Abort(RuntimeError):
|
||||
"""An internal signalling exception that signals Click to abort."""
|
239
click/formatting.py
Normal file
|
@ -0,0 +1,239 @@
|
|||
from contextlib import contextmanager
|
||||
from .termui import get_terminal_size
|
||||
from .parser import split_opt
|
||||
from ._compat import term_len
|
||||
|
||||
|
||||
def measure_table(rows):
|
||||
widths = {}
|
||||
for row in rows:
|
||||
for idx, col in enumerate(row):
|
||||
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
||||
return tuple(y for x, y in sorted(widths.items()))
|
||||
|
||||
|
||||
def iter_rows(rows, col_count):
|
||||
for row in rows:
|
||||
row = tuple(row)
|
||||
yield row + ('',) * (col_count - len(row))
|
||||
|
||||
|
||||
def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
||||
preserve_paragraphs=False):
|
||||
"""A helper function that intelligently wraps text. By default, it
|
||||
assumes that it operates on a single paragraph of text but if the
|
||||
`preserve_paragraphs` parameter is provided it will intelligently
|
||||
handle paragraphs (defined by two empty lines).
|
||||
|
||||
If paragraphs are handled, a paragraph can be prefixed with an empty
|
||||
line containing the ``\\b`` character (``\\x08``) to indicate that
|
||||
no rewrapping should happen in that block.
|
||||
|
||||
:param text: the text that should be rewrapped.
|
||||
:param width: the maximum width for the text.
|
||||
:param initial_indent: the initial indent that should be placed on the
|
||||
first line as a string.
|
||||
:param subsequent_indent: the indent string that should be placed on
|
||||
each consecutive line.
|
||||
:param preserve_paragraphs: if this flag is set then the wrapping will
|
||||
intelligently handle paragraphs.
|
||||
"""
|
||||
from ._textwrap import TextWrapper
|
||||
text = text.expandtabs()
|
||||
wrapper = TextWrapper(width, initial_indent=initial_indent,
|
||||
subsequent_indent=subsequent_indent,
|
||||
replace_whitespace=False)
|
||||
if not preserve_paragraphs:
|
||||
return wrapper.fill(text)
|
||||
|
||||
p = []
|
||||
buf = []
|
||||
indent = None
|
||||
|
||||
def _flush_par():
|
||||
if not buf:
|
||||
return
|
||||
if buf[0].strip() == '\b':
|
||||
p.append((indent or 0, True, '\n'.join(buf[1:])))
|
||||
else:
|
||||
p.append((indent or 0, False, ' '.join(buf)))
|
||||
del buf[:]
|
||||
|
||||
for line in text.splitlines():
|
||||
if not line:
|
||||
_flush_par()
|
||||
indent = None
|
||||
else:
|
||||
if indent is None:
|
||||
orig_len = term_len(line)
|
||||
line = line.lstrip()
|
||||
indent = orig_len - term_len(line)
|
||||
buf.append(line)
|
||||
_flush_par()
|
||||
|
||||
rv = []
|
||||
for indent, raw, text in p:
|
||||
with wrapper.extra_indent(' ' * indent):
|
||||
if raw:
|
||||
rv.append(wrapper.indent_only(text))
|
||||
else:
|
||||
rv.append(wrapper.fill(text))
|
||||
|
||||
return '\n\n'.join(rv)
|
||||
|
||||
|
||||
class HelpFormatter(object):
|
||||
"""This class helps with formatting text-based help pages. It's
|
||||
usually just needed for very special internal cases, but it's also
|
||||
exposed so that developers can write their own fancy outputs.
|
||||
|
||||
At present, it always writes into memory.
|
||||
|
||||
:param indent_increment: the additional increment for each level.
|
||||
:param width: the width for the text. This defaults to the terminal
|
||||
width clamped to a maximum of 78.
|
||||
"""
|
||||
|
||||
def __init__(self, indent_increment=2, width=None):
|
||||
self.indent_increment = indent_increment
|
||||
if width is None:
|
||||
width = max(min(get_terminal_size()[0], 80) - 2, 50)
|
||||
self.width = width
|
||||
self.current_indent = 0
|
||||
self.buffer = []
|
||||
|
||||
def write(self, string):
|
||||
"""Writes a unicode string into the internal buffer."""
|
||||
self.buffer.append(string)
|
||||
|
||||
def indent(self):
|
||||
"""Increases the indentation."""
|
||||
self.current_indent += self.indent_increment
|
||||
|
||||
def dedent(self):
|
||||
"""Decreases the indentation."""
|
||||
self.current_indent -= self.indent_increment
|
||||
|
||||
def write_usage(self, prog, args='', prefix='Usage: '):
|
||||
"""Writes a usage line into the buffer.
|
||||
|
||||
:param prog: the program name.
|
||||
:param args: whitespace separated list of arguments.
|
||||
:param prefix: the prefix for the first line.
|
||||
"""
|
||||
prefix = '%*s%s' % (self.current_indent, prefix, prog)
|
||||
self.write(prefix)
|
||||
|
||||
text_width = max(self.width - self.current_indent - term_len(prefix), 10)
|
||||
indent = ' ' * (term_len(prefix) + 1)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=' ',
|
||||
subsequent_indent=indent))
|
||||
|
||||
self.write('\n')
|
||||
|
||||
def write_heading(self, heading):
|
||||
"""Writes a heading into the buffer."""
|
||||
self.write('%*s%s:\n' % (self.current_indent, '', heading))
|
||||
|
||||
def write_paragraph(self):
|
||||
"""Writes a paragraph into the buffer."""
|
||||
if self.buffer:
|
||||
self.write('\n')
|
||||
|
||||
def write_text(self, text):
|
||||
"""Writes re-indented text into the buffer. This rewraps and
|
||||
preserves paragraphs.
|
||||
"""
|
||||
text_width = max(self.width - self.current_indent, 11)
|
||||
indent = ' ' * self.current_indent
|
||||
self.write(wrap_text(text, text_width,
|
||||
initial_indent=indent,
|
||||
subsequent_indent=indent,
|
||||
preserve_paragraphs=True))
|
||||
self.write('\n')
|
||||
|
||||
def write_dl(self, rows, col_max=30, col_spacing=2):
|
||||
"""Writes a definition list into the buffer. This is how options
|
||||
and commands are usually formatted.
|
||||
|
||||
:param rows: a list of two item tuples for the terms and values.
|
||||
:param col_max: the maximum width of the first column.
|
||||
:param col_spacing: the number of spaces between the first and
|
||||
second column.
|
||||
"""
|
||||
rows = list(rows)
|
||||
widths = measure_table(rows)
|
||||
if len(widths) != 2:
|
||||
raise TypeError('Expected two columns for definition list')
|
||||
|
||||
first_col = min(widths[0], col_max) + col_spacing
|
||||
|
||||
for first, second in iter_rows(rows, len(widths)):
|
||||
self.write('%*s%s' % (self.current_indent, '', first))
|
||||
if not second:
|
||||
self.write('\n')
|
||||
continue
|
||||
if term_len(first) <= first_col - col_spacing:
|
||||
self.write(' ' * (first_col - term_len(first)))
|
||||
else:
|
||||
self.write('\n')
|
||||
self.write(' ' * (first_col + self.current_indent))
|
||||
|
||||
text_width = max(self.width - first_col - 2, 10)
|
||||
lines = iter(wrap_text(second, text_width).splitlines())
|
||||
if lines:
|
||||
self.write(next(lines) + '\n')
|
||||
for line in lines:
|
||||
self.write('%*s%s\n' % (
|
||||
first_col + self.current_indent, '', line))
|
||||
else:
|
||||
self.write('\n')
|
||||
|
||||
@contextmanager
|
||||
def section(self, name):
|
||||
"""Helpful context manager that writes a paragraph, a heading,
|
||||
and the indents.
|
||||
|
||||
:param name: the section name that is written as heading.
|
||||
"""
|
||||
self.write_paragraph()
|
||||
self.write_heading(name)
|
||||
self.indent()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.dedent()
|
||||
|
||||
@contextmanager
|
||||
def indentation(self):
|
||||
"""A context manager that increases the indentation."""
|
||||
self.indent()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.dedent()
|
||||
|
||||
def getvalue(self):
|
||||
"""Returns the buffer contents."""
|
||||
return ''.join(self.buffer)
|
||||
|
||||
|
||||
def join_options(options):
|
||||
"""Given a list of option strings this joins them in the most appropriate
|
||||
way and returns them in the form ``(formatted_string,
|
||||
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
||||
indicates if any of the option prefixes was a slash.
|
||||
"""
|
||||
rv = []
|
||||
any_prefix_is_slash = False
|
||||
for opt in options:
|
||||
prefix = split_opt(opt)[0]
|
||||
if prefix == '/':
|
||||
any_prefix_is_slash = True
|
||||
rv.append((len(prefix), opt))
|
||||
|
||||
rv.sort(key=lambda x: x[0])
|
||||
|
||||
rv = ', '.join(x[1] for x in rv)
|
||||
return rv, any_prefix_is_slash
|
347
click/parser.py
Normal file
|
@ -0,0 +1,347 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
click.parser
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module started out as largely a copy paste from the stdlib's
|
||||
optparse module with the features removed that we do not need from
|
||||
optparse because we implement them in Click on a higher level (for
|
||||
instance type handling, help formatting and a lot more).
|
||||
|
||||
The plan is to remove more and more from here over time.
|
||||
|
||||
The reason this is a different module and not optparse from the stdlib
|
||||
is that there are differences in 2.x and 3.x about the error messages
|
||||
generated and optparse in the stdlib uses gettext for no good reason
|
||||
and might cause us issues.
|
||||
"""
|
||||
import re
|
||||
from .exceptions import UsageError
|
||||
from .utils import unpack_args
|
||||
|
||||
|
||||
def split_opt(opt):
|
||||
first = opt[:1]
|
||||
if first.isalnum():
|
||||
return '', opt
|
||||
if opt[1:2] == first:
|
||||
return opt[:2], opt[2:]
|
||||
return first, opt[1:]
|
||||
|
||||
|
||||
def normalize_opt(opt, ctx):
|
||||
if ctx is None or ctx.token_normalize_func is None:
|
||||
return opt
|
||||
prefix, opt = split_opt(opt)
|
||||
return prefix + ctx.token_normalize_func(opt)
|
||||
|
||||
|
||||
def split_arg_string(string):
|
||||
"""Given an argument string this attempts to split it into small parts."""
|
||||
rv = []
|
||||
for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)"'
|
||||
r'|\S+)\s*', string, re.S):
|
||||
arg = match.group().strip()
|
||||
if arg[:1] == arg[-1:] and arg[:1] in '"\'':
|
||||
arg = arg[1:-1].encode('ascii', 'backslashreplace') \
|
||||
.decode('unicode-escape')
|
||||
try:
|
||||
arg = type(string)(arg)
|
||||
except UnicodeError:
|
||||
pass
|
||||
rv.append(arg)
|
||||
return rv
|
||||
|
||||
|
||||
class Option(object):
|
||||
|
||||
def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None):
|
||||
self._short_opts = []
|
||||
self._long_opts = []
|
||||
self.prefixes = set()
|
||||
|
||||
for opt in opts:
|
||||
prefix, value = split_opt(opt)
|
||||
if not prefix:
|
||||
raise ValueError('Invalid start character for option (%s)'
|
||||
% opt)
|
||||
self.prefixes.add(prefix[0])
|
||||
if len(prefix) == 1 and len(value) == 1:
|
||||
self._short_opts.append(opt)
|
||||
else:
|
||||
self._long_opts.append(opt)
|
||||
self.prefixes.add(prefix)
|
||||
|
||||
if action is None:
|
||||
action = 'store'
|
||||
|
||||
self.dest = dest
|
||||
self.action = action
|
||||
self.nargs = nargs
|
||||
self.const = const
|
||||
self.obj = obj
|
||||
|
||||
@property
|
||||
def takes_value(self):
|
||||
return self.action in ('store', 'append')
|
||||
|
||||
def process(self, value, state):
|
||||
if self.action == 'store':
|
||||
state.opts[self.dest] = value
|
||||
elif self.action == 'store_const':
|
||||
state.opts[self.dest] = self.const
|
||||
elif self.action == 'append':
|
||||
state.opts.setdefault(self.dest, []).append(value)
|
||||
elif self.action == 'append_const':
|
||||
state.opts.setdefault(self.dest, []).append(self.const)
|
||||
elif self.action == 'count':
|
||||
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1
|
||||
else:
|
||||
raise ValueError('unknown action %r' % self.action)
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class Argument(object):
|
||||
|
||||
def __init__(self, dest, nargs=1, obj=None):
|
||||
self.dest = dest
|
||||
self.nargs = nargs
|
||||
self.obj = obj
|
||||
|
||||
def process(self, value, state):
|
||||
state.opts[self.dest] = value
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class ParsingState(object):
|
||||
|
||||
def __init__(self, rargs):
|
||||
self.opts = {}
|
||||
self.largs = []
|
||||
self.rargs = rargs
|
||||
self.order = []
|
||||
|
||||
|
||||
class OptionParser(object):
|
||||
"""The option parser is an internal class that is ultimately used to
|
||||
parse options and arguments. It's modelled after optparse and brings
|
||||
a similar but vastly simplified API. It should generally not be used
|
||||
directly as the high level Click classes wrap it for you.
|
||||
|
||||
It's not nearly as extensible as optparse or argparse as it does not
|
||||
implement features that are implemented on a higher level (such as
|
||||
types or defaults).
|
||||
|
||||
:param ctx: optionally the :class:`~click.Context` where this parser
|
||||
should go with.
|
||||
"""
|
||||
|
||||
def __init__(self, ctx=None):
|
||||
#: The :class:`~click.Context` for this parser. This might be
|
||||
#: `None` for some advanced use cases.
|
||||
self.ctx = ctx
|
||||
#: This controls how the parser deals with interspersed arguments.
|
||||
#: If this is set to `False`, the parser will stop on the first
|
||||
#: non-option. Click uses this to implement nested subcommands
|
||||
#: safely.
|
||||
self.allow_interspersed_args = True
|
||||
self._short_opt = {}
|
||||
self._long_opt = {}
|
||||
self._opt_prefixes = set(['-', '--'])
|
||||
self._args = []
|
||||
|
||||
def add_option(self, opts, dest, action=None, nargs=1, const=None,
|
||||
obj=None):
|
||||
"""Adds a new option named `dest` to the parser. The destination
|
||||
is not inferred (unlike with optparse) and needs to be explicitly
|
||||
provided. Action can be any of ``store``, ``store_const``,
|
||||
``append``, ``appnd_const`` or ``count``.
|
||||
|
||||
The `obj` can be used to identify the option in the order list
|
||||
that is returned from the parser.
|
||||
"""
|
||||
if obj is None:
|
||||
obj = dest
|
||||
opts = [normalize_opt(opt, self.ctx) for opt in opts]
|
||||
option = Option(opts, dest, action=action, nargs=nargs,
|
||||
const=const, obj=obj)
|
||||
self._opt_prefixes.update(option.prefixes)
|
||||
for opt in option._short_opts:
|
||||
self._short_opt[opt] = option
|
||||
for opt in option._long_opts:
|
||||
self._long_opt[opt] = option
|
||||
|
||||
def add_argument(self, dest, nargs=1, obj=None):
|
||||
"""Adds a positional argument named `dest` to the parser.
|
||||
|
||||
The `obj` can be used to identify the option in the order list
|
||||
that is returned from the parser.
|
||||
"""
|
||||
if obj is None:
|
||||
obj = dest
|
||||
self._args.append(Argument(dest=dest, nargs=nargs, obj=obj))
|
||||
|
||||
def parse_args(self, args):
|
||||
"""Parses positional arguments and returns ``(values, args, order)``
|
||||
for the parsed options and arguments as well as the leftover
|
||||
arguments if there are any. The order is a list of objects as they
|
||||
appear on the command line. If arguments appear multiple times they
|
||||
will be memorized multiple times as well.
|
||||
"""
|
||||
state = ParsingState(args)
|
||||
try:
|
||||
self._process_args_for_options(state)
|
||||
self._process_args_for_args(state)
|
||||
except UsageError:
|
||||
if not self.ctx.resilient_parsing:
|
||||
raise
|
||||
return state.opts, state.largs, state.order
|
||||
|
||||
def _process_args_for_args(self, state):
|
||||
pargs, args = unpack_args(state.largs + state.rargs,
|
||||
[x.nargs for x in self._args])
|
||||
|
||||
for idx, arg in enumerate(self._args):
|
||||
arg.process(pargs[idx], state)
|
||||
|
||||
state.largs = args
|
||||
state.rargs = []
|
||||
|
||||
def _process_args_for_options(self, state):
|
||||
while state.rargs:
|
||||
arg = state.rargs.pop(0)
|
||||
arglen = len(arg)
|
||||
# Double dashes always handled explicitly regardless of what
|
||||
# prefixes are valid.
|
||||
if arg == '--':
|
||||
return
|
||||
elif arg[:1] in self._opt_prefixes and arglen > 1:
|
||||
self._process_opts(arg, state)
|
||||
elif self.allow_interspersed_args:
|
||||
state.largs.append(arg)
|
||||
else:
|
||||
state.rargs.insert(0, arg)
|
||||
return
|
||||
|
||||
# Say this is the original argument list:
|
||||
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
|
||||
# ^
|
||||
# (we are about to process arg(i)).
|
||||
#
|
||||
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
|
||||
# [arg0, ..., arg(i-1)] (any options and their arguments will have
|
||||
# been removed from largs).
|
||||
#
|
||||
# The while loop will usually consume 1 or more arguments per pass.
|
||||
# If it consumes 1 (eg. arg is an option that takes no arguments),
|
||||
# then after _process_arg() is done the situation is:
|
||||
#
|
||||
# largs = subset of [arg0, ..., arg(i)]
|
||||
# rargs = [arg(i+1), ..., arg(N-1)]
|
||||
#
|
||||
# If allow_interspersed_args is false, largs will always be
|
||||
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
||||
# not a very interesting subset!
|
||||
|
||||
def _match_long_opt(self, opt):
|
||||
# Is there an exact match?
|
||||
if opt in self._long_opt:
|
||||
return opt
|
||||
|
||||
# Isolate all words with s as a prefix.
|
||||
possibilities = [word for word in self._long_opt
|
||||
if word.startswith(opt)]
|
||||
|
||||
# No exact match, so there had better be just one possibility.
|
||||
if not possibilities:
|
||||
self._error('no such option: %s' % opt)
|
||||
elif len(possibilities) == 1:
|
||||
self._error('no such option: %s. Did you mean %s?' %
|
||||
(opt, possibilities[0]))
|
||||
return possibilities[0]
|
||||
else:
|
||||
# More than one possible completion: ambiguous prefix.
|
||||
possibilities.sort()
|
||||
self._error('no such option: %s. (Possible options: %s)'
|
||||
% (opt, ', '.join(possibilities)))
|
||||
|
||||
def _process_long_opt(self, arg, state):
|
||||
# Value explicitly attached to arg? Pretend it's the next argument.
|
||||
if '=' in arg:
|
||||
opt, next_arg = arg.split('=', 1)
|
||||
state.rargs.insert(0, next_arg)
|
||||
had_explicit_value = True
|
||||
else:
|
||||
opt = arg
|
||||
had_explicit_value = False
|
||||
|
||||
opt = normalize_opt(opt, self.ctx)
|
||||
|
||||
opt = self._match_long_opt(opt)
|
||||
option = self._long_opt[opt]
|
||||
if option.takes_value:
|
||||
nargs = option.nargs
|
||||
if len(state.rargs) < nargs:
|
||||
if nargs == 1:
|
||||
self._error('%s option requires an argument' % opt)
|
||||
else:
|
||||
self._error('%s option requires %d arguments' % (opt, nargs))
|
||||
elif nargs == 1:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
value = tuple(state.rargs[:nargs])
|
||||
del state.rargs[:nargs]
|
||||
|
||||
elif had_explicit_value:
|
||||
self._error('%s option does not take a value' % opt)
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
||||
option.process(value, state)
|
||||
|
||||
def _process_opts(self, arg, state):
|
||||
if '=' in arg or normalize_opt(arg, self.ctx) in self._long_opt:
|
||||
return self._process_long_opt(arg, state)
|
||||
|
||||
stop = False
|
||||
i = 1
|
||||
prefix = arg[0]
|
||||
for ch in arg[1:]:
|
||||
opt = normalize_opt(prefix + ch, self.ctx)
|
||||
option = self._short_opt.get(opt)
|
||||
i += 1
|
||||
|
||||
if not option:
|
||||
self._error('no such option: %s' % (arg if arg.startswith('--') else opt))
|
||||
if option.takes_value:
|
||||
# Any characters left in arg? Pretend they're the
|
||||
# next arg, and stop consuming characters of arg.
|
||||
if i < len(arg):
|
||||
state.rargs.insert(0, arg[i:])
|
||||
stop = True
|
||||
|
||||
nargs = option.nargs
|
||||
if len(state.rargs) < nargs:
|
||||
if nargs == 1:
|
||||
self._error('%s option requires an argument' % opt)
|
||||
else:
|
||||
self._error('%s option requires %d arguments' %
|
||||
(opt, nargs))
|
||||
elif nargs == 1:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
value = tuple(state.rargs[:nargs])
|
||||
del state.rargs[:nargs]
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
||||
option.process(value, state)
|
||||
|
||||
if stop:
|
||||
break
|
||||
|
||||
def _error(self, msg):
|
||||
raise UsageError(msg, self.ctx)
|
496
click/termui.py
Normal file
|
@ -0,0 +1,496 @@
|
|||
import os
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from ._compat import raw_input, text_type, string_types, \
|
||||
colorama, isatty, strip_ansi, get_winterm_size, \
|
||||
DEFAULT_COLUMNS, WIN
|
||||
from .utils import echo
|
||||
from .exceptions import Abort, UsageError
|
||||
from .types import convert_type
|
||||
|
||||
|
||||
# The prompt functions to use. The doc tools currently override these
|
||||
# functions to customize how they work.
|
||||
visible_prompt_func = raw_input
|
||||
|
||||
_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta',
|
||||
'cyan', 'white', 'reset')
|
||||
_ansi_reset_all = '\033[0m'
|
||||
|
||||
|
||||
def hidden_prompt_func(prompt):
|
||||
import getpass
|
||||
return getpass.getpass(prompt)
|
||||
|
||||
|
||||
def _build_prompt(text, suffix, show_default=False, default=None):
|
||||
prompt = text
|
||||
if default is not None and show_default:
|
||||
prompt = '%s [%s]' % (prompt, default)
|
||||
return prompt + suffix
|
||||
|
||||
|
||||
def prompt(text, default=None, hide_input=False,
|
||||
confirmation_prompt=False, type=None,
|
||||
value_proc=None, prompt_suffix=': ', show_default=True):
|
||||
"""Prompts a user for input. This is a convenience function that can
|
||||
be used to prompt a user for input later.
|
||||
|
||||
If the user aborts the input by sending a interrupt signal, this
|
||||
function will catch it and raise a :exc:`Abort` exception.
|
||||
|
||||
:param text: the text to show for the prompt.
|
||||
:param default: the default value to use if no input happens. If this
|
||||
is not given it will prompt until it's aborted.
|
||||
:param hide_input: if this is set to true then the input value will
|
||||
be hidden.
|
||||
:param confirmation_prompt: asks for confirmation for the value.
|
||||
:param type: the type to use to check the value against.
|
||||
:param value_proc: if this parameter is provided it's a function that
|
||||
is invoked instead of the type conversion to
|
||||
convert a value.
|
||||
:param prompt_suffix: a suffix that should be added to the prompt.
|
||||
:param show_default: shows or hides the default value in the prompt.
|
||||
"""
|
||||
result = None
|
||||
|
||||
def prompt_func(text):
|
||||
f = hide_input and hidden_prompt_func or visible_prompt_func
|
||||
try:
|
||||
# Write the prompt separately so that we get nice
|
||||
# coloring through colorama on Windows
|
||||
echo(text, nl=False)
|
||||
return f('')
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
raise Abort()
|
||||
|
||||
if value_proc is None:
|
||||
value_proc = convert_type(type, default)
|
||||
|
||||
prompt = _build_prompt(text, prompt_suffix, show_default, default)
|
||||
|
||||
while 1:
|
||||
while 1:
|
||||
value = prompt_func(prompt)
|
||||
if value:
|
||||
break
|
||||
# If a default is set and used, then the confirmation
|
||||
# prompt is always skipped because that's the only thing
|
||||
# that really makes sense.
|
||||
elif default is not None:
|
||||
return default
|
||||
try:
|
||||
result = value_proc(value)
|
||||
except UsageError as e:
|
||||
echo('Error: %s' % e.message)
|
||||
continue
|
||||
if not confirmation_prompt:
|
||||
return result
|
||||
while 1:
|
||||
value2 = prompt_func('Repeat for confirmation: ')
|
||||
if value2:
|
||||
break
|
||||
if value == value2:
|
||||
return result
|
||||
echo('Error: the two entered values do not match')
|
||||
|
||||
|
||||
def confirm(text, default=False, abort=False, prompt_suffix=': ',
|
||||
show_default=True):
|
||||
"""Prompts for confirmation (yes/no question).
|
||||
|
||||
If the user aborts the input by sending a interrupt signal this
|
||||
function will catch it and raise a :exc:`Abort` exception.
|
||||
|
||||
:param text: the question to ask.
|
||||
:param default: the default for the prompt.
|
||||
:param abort: if this is set to `True` a negative answer aborts the
|
||||
exception by raising :exc:`Abort`.
|
||||
:param prompt_suffix: a suffix that should be added to the prompt.
|
||||
:param show_default: shows or hides the default value in the prompt.
|
||||
"""
|
||||
prompt = _build_prompt(text, prompt_suffix, show_default,
|
||||
default and 'Y/n' or 'y/N')
|
||||
while 1:
|
||||
try:
|
||||
# Write the prompt separately so that we get nice
|
||||
# coloring through colorama on Windows
|
||||
echo(prompt, nl=False)
|
||||
value = visible_prompt_func('').lower().strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
raise Abort()
|
||||
if value in ('y', 'yes'):
|
||||
rv = True
|
||||
elif value in ('n', 'no'):
|
||||
rv = False
|
||||
elif value == '':
|
||||
rv = default
|
||||
else:
|
||||
echo('Error: invalid input')
|
||||
continue
|
||||
break
|
||||
if abort and not rv:
|
||||
raise Abort()
|
||||
return rv
|
||||
|
||||
|
||||
def get_terminal_size():
|
||||
"""Returns the current size of the terminal as tuple in the form
|
||||
``(width, height)`` in columns and rows.
|
||||
"""
|
||||
# If shutil has get_terminal_size() (Python 3.3 and later) use that
|
||||
if sys.version_info >= (3, 3):
|
||||
import shutil
|
||||
shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None)
|
||||
if shutil_get_terminal_size:
|
||||
sz = shutil_get_terminal_size()
|
||||
return sz.columns, sz.lines
|
||||
|
||||
if get_winterm_size is not None:
|
||||
return get_winterm_size()
|
||||
|
||||
def ioctl_gwinsz(fd):
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
cr = struct.unpack(
|
||||
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
||||
except Exception:
|
||||
return
|
||||
return cr
|
||||
|
||||
cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
|
||||
if not cr:
|
||||
try:
|
||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||
try:
|
||||
cr = ioctl_gwinsz(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
if not cr or not cr[0] or not cr[1]:
|
||||
cr = (os.environ.get('LINES', 25),
|
||||
os.environ.get('COLUMNS', DEFAULT_COLUMNS))
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
def echo_via_pager(text, color=None):
|
||||
"""This function takes a text and shows it via an environment specific
|
||||
pager on stdout.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Added the `color` flag.
|
||||
|
||||
:param text: the text to page.
|
||||
:param color: controls if the pager supports ANSI colors or not. The
|
||||
default is autodetection.
|
||||
"""
|
||||
if not isinstance(text, string_types):
|
||||
text = text_type(text)
|
||||
from ._termui_impl import pager
|
||||
return pager(text + '\n', color)
|
||||
|
||||
|
||||
def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
||||
show_percent=None, show_pos=False,
|
||||
item_show_func=None, fill_char='#', empty_char='-',
|
||||
bar_template='%(label)s [%(bar)s] %(info)s',
|
||||
info_sep=' ', width=36, file=None):
|
||||
"""This function creates an iterable context manager that can be used
|
||||
to iterate over something while showing a progress bar. It will
|
||||
either iterate over the `iterable` or `length` items (that are counted
|
||||
up). While iteration happens, this function will print a rendered
|
||||
progress bar to the given `file` (defaults to stdout) and will attempt
|
||||
to calculate remaining time and more. By default, this progress bar
|
||||
will not be rendered if the file is not a terminal.
|
||||
|
||||
The context manager creates the progress bar. When the context
|
||||
manager is entered the progress bar is already displayed. With every
|
||||
iteration over the progress bar, the iterable passed to the bar is
|
||||
advanced and the bar is updated. When the context manager exits,
|
||||
a newline is printed and the progress bar is finalized on screen.
|
||||
|
||||
No printing must happen or the progress bar will be unintentionally
|
||||
destroyed.
|
||||
|
||||
Example usage::
|
||||
|
||||
with progressbar(items) as bar:
|
||||
for item in bar:
|
||||
do_something_with(item)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param iterable: an iterable to iterate over. If not provided the length
|
||||
is required.
|
||||
:param length: the number of items to iterate over. By default the
|
||||
progressbar will attempt to ask the iterator about its
|
||||
length, which might or might not work. If an iterable is
|
||||
also provided this parameter can be used to override the
|
||||
length. If an iterable is not provided the progress bar
|
||||
will iterate over a range of that length.
|
||||
:param label: the label to show next to the progress bar.
|
||||
:param show_eta: enables or disables the estimated time display. This is
|
||||
automatically disabled if the length cannot be
|
||||
determined.
|
||||
:param show_percent: enables or disables the percentage display. The
|
||||
default is `True` if the iterable has a length or
|
||||
`False` if not.
|
||||
:param show_pos: enables or disables the absolute position display. The
|
||||
default is `False`.
|
||||
:param item_show_func: a function called with the current item which
|
||||
can return a string to show the current item
|
||||
next to the progress bar. Note that the current
|
||||
item can be `None`!
|
||||
:param fill_char: the character to use to show the filled part of the
|
||||
progress bar.
|
||||
:param empty_char: the character to use to show the non-filled part of
|
||||
the progress bar.
|
||||
:param bar_template: the format string to use as template for the bar.
|
||||
The parameters in it are ``label`` for the label,
|
||||
``bar`` for the progress bar and ``info`` for the
|
||||
info section.
|
||||
:param info_sep: the separator between multiple info items (eta etc.)
|
||||
:param width: the width of the progress bar in characters, 0 means full
|
||||
terminal width
|
||||
:param file: the file to write to. If this is not a terminal then
|
||||
only the label is printed.
|
||||
"""
|
||||
from ._termui_impl import ProgressBar
|
||||
return ProgressBar(iterable=iterable, length=length, show_eta=show_eta,
|
||||
show_percent=show_percent, show_pos=show_pos,
|
||||
item_show_func=item_show_func, fill_char=fill_char,
|
||||
empty_char=empty_char, bar_template=bar_template,
|
||||
info_sep=info_sep, file=file, label=label,
|
||||
width=width)
|
||||
|
||||
|
||||
def clear():
|
||||
"""Clears the terminal screen. This will have the effect of clearing
|
||||
the whole visible space of the terminal and moving the cursor to the
|
||||
top left. This does not do anything if not connected to a terminal.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
if not isatty(sys.stdout):
|
||||
return
|
||||
# If we're on Windows and we don't have colorama available, then we
|
||||
# clear the screen by shelling out. Otherwise we can use an escape
|
||||
# sequence.
|
||||
if WIN and colorama is None:
|
||||
os.system('cls')
|
||||
else:
|
||||
sys.stdout.write('\033[2J\033[1;1H')
|
||||
|
||||
|
||||
def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
||||
blink=None, reverse=None, reset=True):
|
||||
"""Styles a text with ANSI styles and returns the new string. By
|
||||
default the styling is self contained which means that at the end
|
||||
of the string a reset code is issued. This can be prevented by
|
||||
passing ``reset=False``.
|
||||
|
||||
Examples::
|
||||
|
||||
click.echo(click.style('Hello World!', fg='green'))
|
||||
click.echo(click.style('ATTENTION!', blink=True))
|
||||
click.echo(click.style('Some things', reverse=True, fg='cyan'))
|
||||
|
||||
Supported color names:
|
||||
|
||||
* ``black`` (might be a gray)
|
||||
* ``red``
|
||||
* ``green``
|
||||
* ``yellow`` (might be an orange)
|
||||
* ``blue``
|
||||
* ``magenta``
|
||||
* ``cyan``
|
||||
* ``white`` (might be light gray)
|
||||
* ``reset`` (reset the color code only)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param text: the string to style with ansi codes.
|
||||
:param fg: if provided this will become the foreground color.
|
||||
:param bg: if provided this will become the background color.
|
||||
:param bold: if provided this will enable or disable bold mode.
|
||||
:param dim: if provided this will enable or disable dim mode. This is
|
||||
badly supported.
|
||||
:param underline: if provided this will enable or disable underline.
|
||||
:param blink: if provided this will enable or disable blinking.
|
||||
:param reverse: if provided this will enable or disable inverse
|
||||
rendering (foreground becomes background and the
|
||||
other way round).
|
||||
:param reset: by default a reset-all code is added at the end of the
|
||||
string which means that styles do not carry over. This
|
||||
can be disabled to compose styles.
|
||||
"""
|
||||
bits = []
|
||||
if fg:
|
||||
try:
|
||||
bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30))
|
||||
except ValueError:
|
||||
raise TypeError('Unknown color %r' % fg)
|
||||
if bg:
|
||||
try:
|
||||
bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40))
|
||||
except ValueError:
|
||||
raise TypeError('Unknown color %r' % bg)
|
||||
if bold is not None:
|
||||
bits.append('\033[%dm' % (1 if bold else 22))
|
||||
if dim is not None:
|
||||
bits.append('\033[%dm' % (2 if dim else 22))
|
||||
if underline is not None:
|
||||
bits.append('\033[%dm' % (4 if underline else 24))
|
||||
if blink is not None:
|
||||
bits.append('\033[%dm' % (5 if blink else 25))
|
||||
if reverse is not None:
|
||||
bits.append('\033[%dm' % (7 if reverse else 27))
|
||||
bits.append(text)
|
||||
if reset:
|
||||
bits.append(_ansi_reset_all)
|
||||
return ''.join(bits)
|
||||
|
||||
|
||||
def unstyle(text):
|
||||
"""Removes ANSI styling information from a string. Usually it's not
|
||||
necessary to use this function as Click's echo function will
|
||||
automatically remove styling if necessary.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param text: the text to remove style information from.
|
||||
"""
|
||||
return strip_ansi(text)
|
||||
|
||||
|
||||
def secho(text, file=None, nl=True, err=False, **styles):
|
||||
"""This function combines :func:`echo` and :func:`style` into one
|
||||
call. As such the following two calls are the same::
|
||||
|
||||
click.secho('Hello World!', fg='green')
|
||||
click.echo(click.style('Hello World!', fg='green'))
|
||||
|
||||
All keyword arguments are forwarded to the underlying functions
|
||||
depending on which one they go with.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
text = style(text, **styles)
|
||||
return echo(text, file=file, nl=nl, err=err)
|
||||
|
||||
|
||||
def edit(text=None, editor=None, env=None, require_save=True,
|
||||
extension='.txt', filename=None):
|
||||
r"""Edits the given text in the defined editor. If an editor is given
|
||||
(should be the full path to the executable but the regular operating
|
||||
system search path is used for finding the executable) it overrides
|
||||
the detected editor. Optionally, some environment variables can be
|
||||
used. If the editor is closed without changes, `None` is returned. In
|
||||
case a file is edited directly the return value is always `None` and
|
||||
`require_save` and `extension` are ignored.
|
||||
|
||||
If the editor cannot be opened a :exc:`UsageError` is raised.
|
||||
|
||||
Note for Windows: to simplify cross-platform usage, the newlines are
|
||||
automatically converted from POSIX to Windows and vice versa. As such,
|
||||
the message here will have ``\n`` as newline markers.
|
||||
|
||||
:param text: the text to edit.
|
||||
:param editor: optionally the editor to use. Defaults to automatic
|
||||
detection.
|
||||
:param env: environment variables to forward to the editor.
|
||||
:param require_save: if this is true, then not saving in the editor
|
||||
will make the return value become `None`.
|
||||
:param extension: the extension to tell the editor about. This defaults
|
||||
to `.txt` but changing this might change syntax
|
||||
highlighting.
|
||||
:param filename: if provided it will edit this file instead of the
|
||||
provided text contents. It will not use a temporary
|
||||
file as an indirection in that case.
|
||||
"""
|
||||
from ._termui_impl import Editor
|
||||
editor = Editor(editor=editor, env=env, require_save=require_save,
|
||||
extension=extension)
|
||||
if filename is None:
|
||||
return editor.edit(text)
|
||||
editor.edit_file(filename)
|
||||
|
||||
|
||||
def launch(url, wait=False, locate=False):
|
||||
"""This function launches the given URL (or filename) in the default
|
||||
viewer application for this file type. If this is an executable, it
|
||||
might launch the executable in a new session. The return value is
|
||||
the exit code of the launched application. Usually, ``0`` indicates
|
||||
success.
|
||||
|
||||
Examples::
|
||||
|
||||
click.launch('http://click.pocoo.org/')
|
||||
click.launch('/my/downloaded/file', locate=True)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param url: URL or filename of the thing to launch.
|
||||
:param wait: waits for the program to stop.
|
||||
:param locate: if this is set to `True` then instead of launching the
|
||||
application associated with the URL it will attempt to
|
||||
launch a file manager with the file located. This
|
||||
might have weird effects if the URL does not point to
|
||||
the filesystem.
|
||||
"""
|
||||
from ._termui_impl import open_url
|
||||
return open_url(url, wait=wait, locate=locate)
|
||||
|
||||
|
||||
# If this is provided, getchar() calls into this instead. This is used
|
||||
# for unittesting purposes.
|
||||
_getchar = None
|
||||
|
||||
|
||||
def getchar(echo=False):
|
||||
"""Fetches a single character from the terminal and returns it. This
|
||||
will always return a unicode character and under certain rare
|
||||
circumstances this might return more than one character. The
|
||||
situations which more than one character is returned is when for
|
||||
whatever reason multiple characters end up in the terminal buffer or
|
||||
standard input was not actually a terminal.
|
||||
|
||||
Note that this will always read from the terminal, even if something
|
||||
is piped into the standard input.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param echo: if set to `True`, the character read will also show up on
|
||||
the terminal. The default is to not show it.
|
||||
"""
|
||||
f = _getchar
|
||||
if f is None:
|
||||
from ._termui_impl import getchar as f
|
||||
return f(echo)
|
||||
|
||||
|
||||
def pause(info='Press any key to continue ...'):
|
||||
"""This command stops execution and waits for the user to press any
|
||||
key to continue. This is similar to the Windows batch "pause"
|
||||
command. If the program is not run through a terminal, this command
|
||||
will instead do nothing.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param info: the info string to print before pausing.
|
||||
"""
|
||||
if not isatty(sys.stdin) or not isatty(sys.stdout):
|
||||
return
|
||||
try:
|
||||
if info:
|
||||
echo(info, nl=False)
|
||||
try:
|
||||
getchar()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
pass
|
||||
finally:
|
||||
if info:
|
||||
echo()
|
294
click/testing.py
Normal file
|
@ -0,0 +1,294 @@
|
|||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import contextlib
|
||||
|
||||
from ._compat import iteritems, PY2
|
||||
|
||||
|
||||
# If someone wants to vendor click, we want to ensure the
|
||||
# correct package is discovered. Ideally we could use a
|
||||
# relative import here but unfortunately Python does not
|
||||
# support that.
|
||||
clickpkg = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||
|
||||
|
||||
if PY2:
|
||||
from cStringIO import StringIO
|
||||
else:
|
||||
import io
|
||||
from ._compat import _find_binary_reader
|
||||
|
||||
|
||||
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):
|
||||
self._output.write(rv)
|
||||
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)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._input)
|
||||
|
||||
|
||||
def make_input_stream(input, charset):
|
||||
# Is already an input stream.
|
||||
if hasattr(input, 'read'):
|
||||
if PY2:
|
||||
return input
|
||||
rv = _find_binary_reader(input)
|
||||
if rv is not None:
|
||||
return rv
|
||||
raise TypeError('Could not find binary reader for input stream.')
|
||||
|
||||
if input is None:
|
||||
input = b''
|
||||
elif not isinstance(input, bytes):
|
||||
input = input.encode(charset)
|
||||
if PY2:
|
||||
return StringIO(input)
|
||||
return io.BytesIO(input)
|
||||
|
||||
|
||||
class Result(object):
|
||||
"""Holds the captured result of an invoked CLI script."""
|
||||
|
||||
def __init__(self, runner, output_bytes, exit_code, exception,
|
||||
exc_info=None):
|
||||
#: The runner that created the result
|
||||
self.runner = runner
|
||||
#: The output as bytes.
|
||||
self.output_bytes = output_bytes
|
||||
#: The exit code as integer.
|
||||
self.exit_code = exit_code
|
||||
#: The exception that happend if one did.
|
||||
self.exception = exception
|
||||
#: The traceback
|
||||
self.exc_info = exc_info
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""The output as unicode string."""
|
||||
return self.output_bytes.decode(self.runner.charset, 'replace') \
|
||||
.replace('\r\n', '\n')
|
||||
|
||||
def __repr__(self):
|
||||
return '<Result %s>' % (
|
||||
self.exception and repr(self.exception) or 'okay',
|
||||
)
|
||||
|
||||
|
||||
class CliRunner(object):
|
||||
"""The CLI runner provides functionality to invoke a Click command line
|
||||
script for unittesting purposes in a isolated environment. This only
|
||||
works in single-threaded systems without any concurrency as it changes the
|
||||
global interpreter state.
|
||||
|
||||
:param charset: the character set for the input and output data. This is
|
||||
UTF-8 by default and should not be changed currently as
|
||||
the reporting to Click only works in Python 2 properly.
|
||||
:param env: a dictionary with environment variables for overriding.
|
||||
:param echo_stdin: if this is set to `True`, then reading from stdin writes
|
||||
to stdout. This is useful for showing examples in
|
||||
some circumstances. Note that regular prompts
|
||||
will automatically echo the input.
|
||||
"""
|
||||
|
||||
def __init__(self, charset=None, env=None, echo_stdin=False):
|
||||
if charset is None:
|
||||
charset = 'utf-8'
|
||||
self.charset = charset
|
||||
self.env = env or {}
|
||||
self.echo_stdin = echo_stdin
|
||||
|
||||
def get_default_prog_name(self, cli):
|
||||
"""Given a command object it will return the default program name
|
||||
for it. The default is the `name` attribute or ``"root"`` if not
|
||||
set.
|
||||
"""
|
||||
return cli.name or 'root'
|
||||
|
||||
def make_env(self, overrides=None):
|
||||
"""Returns the environment overrides for invoking a script."""
|
||||
rv = dict(self.env)
|
||||
if overrides:
|
||||
rv.update(overrides)
|
||||
return rv
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolation(self, input=None, env=None):
|
||||
"""A context manager that sets up the isolation for invoking of a
|
||||
command line tool. This sets up stdin with the given input data
|
||||
and `os.environ` with the overrides from the given dictionary.
|
||||
This also rebinds some internals in Click to be mocked (like the
|
||||
prompt functionality).
|
||||
|
||||
This is automatically done in the :meth:`invoke` method.
|
||||
|
||||
:param input: the input stream to put into sys.stdin.
|
||||
:param env: the environment overrides as dictionary.
|
||||
"""
|
||||
input = make_input_stream(input, self.charset)
|
||||
|
||||
old_stdin = sys.stdin
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
|
||||
env = self.make_env(env)
|
||||
|
||||
if PY2:
|
||||
sys.stdout = sys.stderr = bytes_output = StringIO()
|
||||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
else:
|
||||
bytes_output = io.BytesIO()
|
||||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||
sys.stdout = sys.stderr = io.TextIOWrapper(
|
||||
bytes_output, encoding=self.charset)
|
||||
|
||||
sys.stdin = input
|
||||
|
||||
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')
|
||||
|
||||
def _getchar(echo):
|
||||
char = sys.stdin.read(1)
|
||||
if echo:
|
||||
sys.stdout.write(char)
|
||||
sys.stdout.flush()
|
||||
return char
|
||||
|
||||
old_visible_prompt_func = clickpkg.termui.visible_prompt_func
|
||||
old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func
|
||||
old__getchar_func = clickpkg.termui._getchar
|
||||
clickpkg.termui.visible_prompt_func = visible_input
|
||||
clickpkg.termui.hidden_prompt_func = hidden_input
|
||||
clickpkg.termui._getchar = _getchar
|
||||
|
||||
old_env = {}
|
||||
try:
|
||||
for key, value in iteritems(env):
|
||||
old_env[key] = os.environ.get(value)
|
||||
if value is None:
|
||||
try:
|
||||
del os.environ[key]
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
os.environ[key] = value
|
||||
yield bytes_output
|
||||
finally:
|
||||
for key, value in iteritems(old_env):
|
||||
if value is None:
|
||||
try:
|
||||
del os.environ[key]
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
os.environ[key] = value
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
sys.stdin = old_stdin
|
||||
clickpkg.termui.visible_prompt_func = old_visible_prompt_func
|
||||
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
|
||||
clickpkg.termui._getchar = old__getchar_func
|
||||
|
||||
def invoke(self, cli, args=None, input=None, env=None,
|
||||
catch_exceptions=True, **extra):
|
||||
"""Invokes a command in an isolated environment. The arguments are
|
||||
forwarded directly to the command line script, the `extra` keyword
|
||||
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
||||
the command.
|
||||
|
||||
This returns a :class:`Result` object.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
The ``catch_exceptions`` parameter was added.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The result object now has an `exc_info` attribute with the
|
||||
traceback if available.
|
||||
|
||||
:param cli: the command to invoke
|
||||
:param args: the arguments to invoke
|
||||
:param input: the input data for `sys.stdin`.
|
||||
:param env: the environment overrides.
|
||||
:param catch_exceptions: Whether to catch any other exceptions than
|
||||
``SystemExit``.
|
||||
:param extra: the keyword arguments to pass to :meth:`main`.
|
||||
"""
|
||||
exc_info = None
|
||||
with self.isolation(input=input, env=env) as out:
|
||||
exception = None
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
cli.main(args=args or (),
|
||||
prog_name=self.get_default_prog_name(cli), **extra)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
exception = e
|
||||
exit_code = e.code
|
||||
exc_info = sys.exc_info()
|
||||
except Exception as e:
|
||||
if not catch_exceptions:
|
||||
raise
|
||||
exception = e
|
||||
exit_code = -1
|
||||
exc_info = sys.exc_info()
|
||||
finally:
|
||||
sys.stdout.flush()
|
||||
output = out.getvalue()
|
||||
|
||||
return Result(runner=self,
|
||||
output_bytes=output,
|
||||
exit_code=exit_code,
|
||||
exception=exception,
|
||||
exc_info=exc_info)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolated_filesystem(self):
|
||||
"""A context manager that creates a temporary folder and changes
|
||||
the current working directory to it for isolated filesystem tests.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
t = tempfile.mkdtemp()
|
||||
os.chdir(t)
|
||||
try:
|
||||
yield t
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
try:
|
||||
shutil.rmtree(t)
|
||||
except (OSError, IOError):
|
||||
pass
|
451
click/types.py
Normal file
|
@ -0,0 +1,451 @@
|
|||
import os
|
||||
import sys
|
||||
import stat
|
||||
|
||||
from ._compat import open_stream, text_type, filename_to_ui, get_streerror
|
||||
from .exceptions import BadParameter
|
||||
from .utils import safecall, LazyFile
|
||||
|
||||
|
||||
class ParamType(object):
|
||||
"""Helper for converting values through types. The following is
|
||||
necessary for a valid type:
|
||||
|
||||
* it needs a name
|
||||
* it needs to pass through None unchanged
|
||||
* it needs to convert from a string
|
||||
* it needs to convert its result type through unchanged
|
||||
(eg: needs to be idempotent)
|
||||
* it needs to be able to deal with param and context being `None`.
|
||||
This can be the case when the object is used with prompt
|
||||
inputs.
|
||||
"""
|
||||
|
||||
#: the descriptive name of this type
|
||||
name = None
|
||||
|
||||
#: if a list of this type is expected and the value is pulled from a
|
||||
#: string environment variable, this is what splits it up. `None`
|
||||
#: means any whitespace. For all parameters the general rule is that
|
||||
#: whitespace splits them up. The exception are paths and files which
|
||||
#: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
|
||||
#: Windows).
|
||||
envvar_list_splitter = None
|
||||
|
||||
def __call__(self, value, param=None, ctx=None):
|
||||
if value is not None:
|
||||
return self.convert(value, param, ctx)
|
||||
|
||||
def get_metavar(self, param):
|
||||
"""Returns the metavar default for this param if it provides one."""
|
||||
|
||||
def get_missing_message(self, param):
|
||||
"""Optionally might return extra information about a missing
|
||||
parameter.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
"""Converts the value. This is not invoked for values that are
|
||||
`None` (the missing value).
|
||||
"""
|
||||
return value
|
||||
|
||||
def split_envvar_value(self, rv):
|
||||
"""Given a value from an environment variable this splits it up
|
||||
into small chunks depending on the defined envvar list splitter.
|
||||
|
||||
If the splitter is set to `None`, which means that whitespace splits,
|
||||
then leading and trailing whitespace is ignored. Otherwise, leading
|
||||
and trailing splitters usually lead to empty items being included.
|
||||
"""
|
||||
return (rv or '').split(self.envvar_list_splitter)
|
||||
|
||||
def fail(self, message, param=None, ctx=None):
|
||||
"""Helper method to fail with an invalid value message."""
|
||||
raise BadParameter(message, ctx=ctx, param=param)
|
||||
|
||||
|
||||
class FuncParamType(ParamType):
|
||||
|
||||
def __init__(self, func):
|
||||
self.name = func.__name__
|
||||
self.func = func
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return self.func(value)
|
||||
except ValueError:
|
||||
try:
|
||||
value = text_type(value)
|
||||
except UnicodeError:
|
||||
value = str(value).decode('utf-8', 'replace')
|
||||
self.fail(value, param, ctx)
|
||||
|
||||
|
||||
class StringParamType(ParamType):
|
||||
name = 'text'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
enc = getattr(sys.stdin, 'encoding', None)
|
||||
if enc is not None:
|
||||
value = value.decode(enc)
|
||||
except UnicodeError:
|
||||
try:
|
||||
value = value.decode(sys.getfilesystemencoding())
|
||||
except UnicodeError:
|
||||
value = value.decode('utf-8', 'replace')
|
||||
return value
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
return 'STRING'
|
||||
|
||||
|
||||
class Choice(ParamType):
|
||||
"""The choice type allows a value to checked against a fixed set of
|
||||
supported values. All of these values have to be strings.
|
||||
|
||||
See :ref:`choice-opts` for an example.
|
||||
"""
|
||||
name = 'choice'
|
||||
|
||||
def __init__(self, choices):
|
||||
self.choices = choices
|
||||
|
||||
def get_metavar(self, param):
|
||||
return '[%s]' % '|'.join(self.choices)
|
||||
|
||||
def get_missing_message(self, param):
|
||||
return 'Choose from %s.' % ', '.join(self.choices)
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
# Exact match
|
||||
if value in self.choices:
|
||||
return value
|
||||
|
||||
# Match through normalization
|
||||
if ctx is not None and \
|
||||
ctx.token_normalize_func is not None:
|
||||
value = ctx.token_normalize_func(value)
|
||||
for choice in self.choices:
|
||||
if ctx.token_normalize_func(choice) == value:
|
||||
return choice
|
||||
|
||||
self.fail('invalid choice: %s. (choose from %s)' %
|
||||
(value, ', '.join(self.choices)), param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Choice(%r)' % list(self.choices)
|
||||
|
||||
|
||||
class IntParamType(ParamType):
|
||||
name = 'integer'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'INT'
|
||||
|
||||
|
||||
class IntRange(IntParamType):
|
||||
"""A parameter that works similar to :data:`click.INT` 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 = 'integer 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 = IntParamType.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 'IntRange(%r, %r)' % (self.min, self.max)
|
||||
|
||||
|
||||
class BoolParamType(ParamType):
|
||||
name = 'boolean'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bool):
|
||||
return bool(value)
|
||||
value = value.lower()
|
||||
if value in ('true', '1', 'yes', 'y'):
|
||||
return True
|
||||
elif value in ('false', '0', 'no', 'n'):
|
||||
return False
|
||||
self.fail('%s is not a valid boolean' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'BOOL'
|
||||
|
||||
|
||||
class FloatParamType(ParamType):
|
||||
name = 'float'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
self.fail('%s is not a valid floating point value' %
|
||||
value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'FLOAT'
|
||||
|
||||
|
||||
class UUIDParameterType(ParamType):
|
||||
name = 'uuid'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
import uuid
|
||||
try:
|
||||
return uuid.UUID(value)
|
||||
except ValueError:
|
||||
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'UUID'
|
||||
|
||||
|
||||
class File(ParamType):
|
||||
"""Declares a parameter to be a file for reading or writing. The file
|
||||
is automatically closed once the context tears down (after the command
|
||||
finished working).
|
||||
|
||||
Files can be opened for reading or writing. The special value ``-``
|
||||
indicates stdin or stdout depending on the mode.
|
||||
|
||||
By default, the file is opened for reading text data, but it can also be
|
||||
opened in binary mode or for writing. The encoding parameter can be used
|
||||
to force a specific encoding.
|
||||
|
||||
The `lazy` flag controls if the file should be opened immediately or
|
||||
upon first IO. The default is to be non lazy for standard input and
|
||||
output streams as well as files opened for reading, lazy otherwise.
|
||||
|
||||
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
|
||||
completion the file will be moved over to the original location. This
|
||||
is useful if a file regularly read by other users is modified.
|
||||
|
||||
See :ref:`file-args` for more information.
|
||||
"""
|
||||
name = 'filename'
|
||||
envvar_list_splitter = os.path.pathsep
|
||||
|
||||
def __init__(self, mode='r', encoding=None, errors='strict', lazy=None,
|
||||
atomic=False):
|
||||
self.mode = mode
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
self.lazy = lazy
|
||||
self.atomic = atomic
|
||||
|
||||
def resolve_lazy_flag(self, value):
|
||||
if self.lazy is not None:
|
||||
return self.lazy
|
||||
if value == '-':
|
||||
return False
|
||||
elif 'w' in self.mode:
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
if hasattr(value, 'read') or hasattr(value, 'write'):
|
||||
return value
|
||||
|
||||
lazy = self.resolve_lazy_flag(value)
|
||||
|
||||
if lazy:
|
||||
f = LazyFile(value, self.mode, self.encoding, self.errors,
|
||||
atomic=self.atomic)
|
||||
if ctx is not None:
|
||||
ctx.call_on_close(f.close_intelligently)
|
||||
return f
|
||||
|
||||
f, should_close = open_stream(value, self.mode,
|
||||
self.encoding, self.errors,
|
||||
atomic=self.atomic)
|
||||
# If a context is provided, we automatically close the file
|
||||
# at the end of the context execution (or flush out). If a
|
||||
# context does not exist, it's the caller's responsibility to
|
||||
# properly close the file. This for instance happens when the
|
||||
# type is used with prompts.
|
||||
if ctx is not None:
|
||||
if should_close:
|
||||
ctx.call_on_close(safecall(f.close))
|
||||
else:
|
||||
ctx.call_on_close(safecall(f.flush))
|
||||
return f
|
||||
except (IOError, OSError) as e:
|
||||
self.fail('Could not open file: %s: %s' % (
|
||||
filename_to_ui(value),
|
||||
get_streerror(e),
|
||||
), param, ctx)
|
||||
|
||||
|
||||
class Path(ParamType):
|
||||
"""The path type is similar to the :class:`File` type but it performs
|
||||
different checks. First of all, instead of returning a open file
|
||||
handle it returns just the filename. Secondly, it can perform various
|
||||
basic checks about what the file or directory should be.
|
||||
|
||||
:param exists: if set to true, the file or directory needs to exist for
|
||||
this value to be valid. If this is not required and a
|
||||
file does indeed not exist, then all further checks are
|
||||
silently skipped.
|
||||
:param file_okay: controls if a file is a possible value.
|
||||
:param dir_okay: controls if a directory is a possible value.
|
||||
:param writable: if true, a writable check is performed.
|
||||
:param readable: if true, a readable check is performed.
|
||||
:param resolve_path: if this is true, then the path is fully resolved
|
||||
before the value is passed onwards. This means
|
||||
that it's absolute and symlinks are resolved.
|
||||
"""
|
||||
envvar_list_splitter = os.path.pathsep
|
||||
|
||||
def __init__(self, exists=False, file_okay=True, dir_okay=True,
|
||||
writable=False, readable=True, resolve_path=False):
|
||||
self.exists = exists
|
||||
self.file_okay = file_okay
|
||||
self.dir_okay = dir_okay
|
||||
self.writable = writable
|
||||
self.readable = readable
|
||||
self.resolve_path = resolve_path
|
||||
|
||||
if self.file_okay and not self.dir_okay:
|
||||
self.name = 'file'
|
||||
self.path_type = 'File'
|
||||
if self.dir_okay and not self.file_okay:
|
||||
self.name = 'directory'
|
||||
self.path_type = 'Directory'
|
||||
else:
|
||||
self.name = 'path'
|
||||
self.path_type = 'Path'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
rv = value
|
||||
if self.resolve_path:
|
||||
rv = os.path.realpath(rv)
|
||||
|
||||
try:
|
||||
st = os.stat(rv)
|
||||
except OSError:
|
||||
if not self.exists:
|
||||
return rv
|
||||
self.fail('%s "%s" does not exist.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
|
||||
if not self.file_okay and stat.S_ISREG(st.st_mode):
|
||||
self.fail('%s "%s" is a file.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
||||
self.fail('%s "%s" is a directory.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
if self.writable and not os.access(value, os.W_OK):
|
||||
self.fail('%s "%s" is not writable.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
if self.readable and not os.access(value, os.R_OK):
|
||||
self.fail('%s "%s" is not readable.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def convert_type(ty, default=None):
|
||||
"""Converts a callable or python ty into the most appropriate param
|
||||
ty.
|
||||
"""
|
||||
if isinstance(ty, ParamType):
|
||||
return ty
|
||||
guessed_type = False
|
||||
if ty is None and default is not None:
|
||||
ty = type(default)
|
||||
guessed_type = True
|
||||
if ty is text_type or ty is str or ty is None:
|
||||
return STRING
|
||||
if ty is int:
|
||||
return INT
|
||||
# Booleans are only okay if not guessed. This is done because for
|
||||
# flags the default value is actually a bit of a lie in that it
|
||||
# indicates which of the flags is the one we want. See get_default()
|
||||
# for more information.
|
||||
if ty is bool and not guessed_type:
|
||||
return BOOL
|
||||
if ty is float:
|
||||
return FLOAT
|
||||
if guessed_type:
|
||||
return STRING
|
||||
|
||||
# Catch a common mistake
|
||||
if __debug__:
|
||||
try:
|
||||
if issubclass(ty, ParamType):
|
||||
raise AssertionError('Attempted to use an uninstantiated '
|
||||
'parameter type (%s).' % ty)
|
||||
except TypeError:
|
||||
pass
|
||||
return FuncParamType(ty)
|
||||
|
||||
|
||||
#: A unicode string parameter type which is the implicit default. This
|
||||
#: can also be selected by using ``str`` as type.
|
||||
STRING = StringParamType()
|
||||
|
||||
#: An integer parameter. This can also be selected by using ``int`` as
|
||||
#: type.
|
||||
INT = IntParamType()
|
||||
|
||||
#: A floating point value parameter. This can also be selected by using
|
||||
#: ``float`` as type.
|
||||
FLOAT = FloatParamType()
|
||||
|
||||
#: A boolean parameter. This is the default for boolean flags. This can
|
||||
#: also be selected by using ``bool`` as a type.
|
||||
BOOL = BoolParamType()
|
||||
|
||||
#: A UUID parameter.
|
||||
UUID = UUIDParameterType()
|
425
click/utils.py
Normal file
|
@ -0,0 +1,425 @@
|
|||
import os
|
||||
import sys
|
||||
from collections import deque
|
||||
|
||||
from ._compat import text_type, open_stream, get_streerror, string_types, \
|
||||
PY2, binary_streams, text_streams, filename_to_ui, \
|
||||
auto_wrap_for_ansi, strip_ansi, isatty, _default_text_stdout, \
|
||||
_default_text_stderr, is_bytes, WIN
|
||||
|
||||
if not PY2:
|
||||
from ._compat import _find_binary_writer
|
||||
|
||||
|
||||
echo_native_types = string_types + (bytes, bytearray)
|
||||
|
||||
|
||||
def _posixify(name):
|
||||
return '-'.join(name.split()).lower()
|
||||
|
||||
|
||||
def unpack_args(args, nargs_spec):
|
||||
"""Given an iterable of arguments and an iterable of nargs specifications,
|
||||
it returns a tuple with all the unpacked arguments at the first index
|
||||
and all remaining arguments as the second.
|
||||
|
||||
The nargs specification is the number of arguments that should be consumed
|
||||
or `-1` to indicate that this position should eat up all the remainders.
|
||||
|
||||
Missing items are filled with `None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> unpack_args(range(6), [1, 2, 1, -1])
|
||||
((0, (1, 2), 3, (4, 5)), [])
|
||||
>>> unpack_args(range(6), [1, 2, 1])
|
||||
((0, (1, 2), 3), [4, 5])
|
||||
>>> unpack_args(range(6), [-1])
|
||||
(((0, 1, 2, 3, 4, 5),), [])
|
||||
>>> unpack_args(range(6), [1, 1])
|
||||
((0, 1), [2, 3, 4, 5])
|
||||
"""
|
||||
args = deque(args)
|
||||
nargs_spec = deque(nargs_spec)
|
||||
rv = []
|
||||
spos = None
|
||||
|
||||
def _fetch(c):
|
||||
try:
|
||||
return (spos is not None and c.pop() or c.popleft())
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
while nargs_spec:
|
||||
nargs = _fetch(nargs_spec)
|
||||
if nargs == 1:
|
||||
rv.append(_fetch(args))
|
||||
elif nargs > 1:
|
||||
x = [_fetch(args) for _ in range(nargs)]
|
||||
# If we're reversed, we're pulling in the arguments in reverse,
|
||||
# so we need to turn them around.
|
||||
if spos is not None:
|
||||
x.reverse()
|
||||
rv.append(tuple(x))
|
||||
elif nargs < 0:
|
||||
if spos is not None:
|
||||
raise TypeError('Cannot have two nargs < 0')
|
||||
spos = len(rv)
|
||||
rv.append(None)
|
||||
|
||||
# spos is the position of the wildcard (star). If it's not `None`,
|
||||
# we fill it with the remainder.
|
||||
if spos is not None:
|
||||
rv[spos] = tuple(args)
|
||||
args = []
|
||||
|
||||
return tuple(rv), list(args)
|
||||
|
||||
|
||||
def safecall(func):
|
||||
"""Wraps a function so that it swallows exceptions."""
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
return wrapper
|
||||
|
||||
|
||||
def make_str(value):
|
||||
"""Converts a value into a valid string."""
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode(sys.getfilesystemencoding())
|
||||
except UnicodeError:
|
||||
return value.decode('utf-8', 'replace')
|
||||
return text_type(value)
|
||||
|
||||
|
||||
def make_default_short_help(help, max_length=45):
|
||||
words = help.split()
|
||||
total_length = 0
|
||||
result = []
|
||||
done = False
|
||||
|
||||
for word in words:
|
||||
if word[-1:] == '.':
|
||||
done = True
|
||||
new_length = result and 1 + len(word) or len(word)
|
||||
if total_length + new_length > max_length:
|
||||
result.append('...')
|
||||
done = True
|
||||
else:
|
||||
if result:
|
||||
result.append(' ')
|
||||
result.append(word)
|
||||
if done:
|
||||
break
|
||||
total_length += new_length
|
||||
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
class LazyFile(object):
|
||||
"""A lazy file works like a regular file but it does not fully open
|
||||
the file but it does perform some basic checks early to see if the
|
||||
filename parameter does make sense. This is useful for safely opening
|
||||
files for writing.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, mode='r', encoding=None, errors='strict',
|
||||
atomic=False):
|
||||
self.name = filename
|
||||
self.mode = mode
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
self.atomic = atomic
|
||||
|
||||
if filename == '-':
|
||||
self._f, self.should_close = open_stream(filename, mode,
|
||||
encoding, errors)
|
||||
else:
|
||||
if 'r' in mode:
|
||||
# Open and close the file in case we're opening it for
|
||||
# reading so that we can catch at least some errors in
|
||||
# some cases early.
|
||||
open(filename, mode).close()
|
||||
self._f = None
|
||||
self.should_close = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.open(), name)
|
||||
|
||||
def __repr__(self):
|
||||
if self._f is not None:
|
||||
return repr(self._f)
|
||||
return '<unopened file %r %s>' % (self.name, self.mode)
|
||||
|
||||
def open(self):
|
||||
"""Opens the file if it's not yet open. This call might fail with
|
||||
a :exc:`FileError`. Not handling this error will produce an error
|
||||
that Click shows.
|
||||
"""
|
||||
if self._f is not None:
|
||||
return self._f
|
||||
try:
|
||||
rv, self.should_close = open_stream(self.name, self.mode,
|
||||
self.encoding,
|
||||
self.errors,
|
||||
atomic=self.atomic)
|
||||
except (IOError, OSError) as e:
|
||||
from .exceptions import FileError
|
||||
raise FileError(self.name, hint=get_streerror(e))
|
||||
self._f = rv
|
||||
return rv
|
||||
|
||||
def close(self):
|
||||
"""Closes the underlying file, no matter what."""
|
||||
if self._f is not None:
|
||||
self._f.close()
|
||||
|
||||
def close_intelligently(self):
|
||||
"""This function only closes the file if it was opened by the lazy
|
||||
file wrapper. For instance this will never close stdin.
|
||||
"""
|
||||
if self.should_close:
|
||||
self.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close_intelligently()
|
||||
|
||||
|
||||
class KeepOpenFile(object):
|
||||
|
||||
def __init__(self, file):
|
||||
self._file = file
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._file, name)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._file)
|
||||
|
||||
|
||||
def echo(message=None, file=None, nl=True, err=False):
|
||||
"""Prints a message plus a newline to the given file or stdout. On
|
||||
first sight, this looks like the print function, but it has improved
|
||||
support for handling Unicode and binary data that does not fail no
|
||||
matter how badly configured the system is.
|
||||
|
||||
Primarily it means that you can print binary data as well as Unicode
|
||||
data on both 2.x and 3.x to the given file in the most appropriate way
|
||||
possible. This is a very carefree function as in that it will try its
|
||||
best to not fail.
|
||||
|
||||
In addition to that, if `colorama`_ is installed, the echo function will
|
||||
also support clever handling of ANSI codes. Essentially it will then
|
||||
do the following:
|
||||
|
||||
- add transparent handling of ANSI color codes on Windows.
|
||||
- hide ANSI codes automatically if the destination file is not a
|
||||
terminal.
|
||||
|
||||
.. _colorama: http://pypi.python.org/pypi/colorama
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Starting with version 2.0 of Click, the echo function will work
|
||||
with colorama if it's installed.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
The `err` parameter was added.
|
||||
|
||||
:param message: the message to print
|
||||
:param file: the file to write to (defaults to ``stdout``)
|
||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||
``stdout``. This is faster and easier than calling
|
||||
:func:`get_text_stderr` yourself.
|
||||
:param nl: if set to `True` (the default) a newline is printed afterwards.
|
||||
"""
|
||||
if file is None:
|
||||
if err:
|
||||
file = _default_text_stderr()
|
||||
else:
|
||||
file = _default_text_stdout()
|
||||
|
||||
# Convert non bytes/text into the native string type.
|
||||
if message is not None and not isinstance(message, echo_native_types):
|
||||
message = text_type(message)
|
||||
|
||||
# If there is a message, and we're in Python 3, and the value looks
|
||||
# like bytes, we manually need to find the binary stream and write the
|
||||
# message in there. This is done separately so that most stream
|
||||
# types will work as you would expect. Eg: you can write to StringIO
|
||||
# for other cases.
|
||||
if message and not PY2 and is_bytes(message):
|
||||
binary_file = _find_binary_writer(file)
|
||||
if binary_file is not None:
|
||||
file.flush()
|
||||
binary_file.write(message)
|
||||
if nl:
|
||||
binary_file.write(b'\n')
|
||||
binary_file.flush()
|
||||
return
|
||||
|
||||
# ANSI-style support. If there is no message or we are dealing with
|
||||
# bytes nothing is happening. If we are connected to a file we want
|
||||
# to strip colors. If we are on windows we either wrap the stream
|
||||
# to strip the color or we use the colorama support to translate the
|
||||
# ansi codes to API calls.
|
||||
if message and not is_bytes(message):
|
||||
if not isatty(file):
|
||||
message = strip_ansi(message)
|
||||
elif WIN:
|
||||
if auto_wrap_for_ansi is not None:
|
||||
file = auto_wrap_for_ansi(file)
|
||||
else:
|
||||
message = strip_ansi(message)
|
||||
|
||||
if message:
|
||||
file.write(message)
|
||||
if nl:
|
||||
file.write('\n')
|
||||
file.flush()
|
||||
|
||||
|
||||
def get_binary_stream(name):
|
||||
"""Returns a system stream for byte processing. This essentially
|
||||
returns the stream from the sys module with the given name but it
|
||||
solves some compatibility issues between different Python versions.
|
||||
Primarily this function is necessary for getting binary streams on
|
||||
Python 3.
|
||||
|
||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||
``'stdout'`` and ``'stderr'``
|
||||
"""
|
||||
opener = binary_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError('Unknown standard stream %r' % name)
|
||||
return opener()
|
||||
|
||||
|
||||
def get_text_stream(name, encoding=None, errors='strict'):
|
||||
"""Returns a system stream for text processing. This usually returns
|
||||
a wrapped stream around a binary stream returned from
|
||||
:func:`get_binary_stream` but it also can take shortcuts on Python 3
|
||||
for already correctly configured streams.
|
||||
|
||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||
``'stdout'`` and ``'stderr'``
|
||||
:param encoding: overrides the detected default encoding.
|
||||
:param errors: overrides the default error mode.
|
||||
"""
|
||||
opener = text_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError('Unknown standard stream %r' % name)
|
||||
return opener(encoding, errors)
|
||||
|
||||
|
||||
def open_file(filename, mode='r', encoding=None, errors='strict',
|
||||
lazy=False, atomic=False):
|
||||
"""This is similar to how the :class:`File` works but for manual
|
||||
usage. Files are opened non lazy by default. This can open regular
|
||||
files as well as stdin/stdout if ``'-'`` is passed.
|
||||
|
||||
If stdin/stdout is returned the stream is wrapped so that the context
|
||||
manager will not close the stream accidentally. This makes it possible
|
||||
to always use the function like this without having to worry to
|
||||
accidentally close a standard stream::
|
||||
|
||||
with open_file(filename) as f:
|
||||
...
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
:param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
|
||||
:param mode: the mode in which to open the file.
|
||||
:param encoding: the encoding to use.
|
||||
:param errors: the error handling for this file.
|
||||
:param lazy: can be flipped to true to open the file lazily.
|
||||
:param atomic: in atomic mode writes go into a temporary file and it's
|
||||
moved on close.
|
||||
"""
|
||||
if lazy:
|
||||
return LazyFile(filename, mode, encoding, errors, atomic=atomic)
|
||||
f, should_close = open_stream(filename, mode, encoding, errors,
|
||||
atomic=atomic)
|
||||
if not should_close:
|
||||
f = KeepOpenFile(f)
|
||||
return f
|
||||
|
||||
|
||||
def format_filename(filename, shorten=False):
|
||||
"""Formats a filename for user display. The main purpose of this
|
||||
function is to ensure that the filename can be displayed at all. This
|
||||
will decode the filename to unicode if necessary in a way that it will
|
||||
not fail. Optionally, it can shorten the filename to not include the
|
||||
full path to the filename.
|
||||
|
||||
:param filename: formats a filename for UI display. This will also convert
|
||||
the filename into unicode without failing.
|
||||
:param shorten: this optionally shortens the filename to strip of the
|
||||
path that leads up to it.
|
||||
"""
|
||||
if shorten:
|
||||
filename = os.path.basename(filename)
|
||||
return filename_to_ui(filename)
|
||||
|
||||
|
||||
def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
r"""Returns the config folder for the application. The default behavior
|
||||
is to return whatever is most appropriate for the operating system.
|
||||
|
||||
To give you an idea, for an app called ``"Foo Bar"``, something like
|
||||
the following folders could be returned:
|
||||
|
||||
Mac OS X:
|
||||
``~/Library/Application Support/Foo Bar``
|
||||
Mac OS X (POSIX):
|
||||
``~/.foo-bar``
|
||||
Unix:
|
||||
``~/.config/foo-bar``
|
||||
Unix (POSIX):
|
||||
``~/.foo-bar``
|
||||
Win XP (roaming):
|
||||
``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
|
||||
Win XP (not roaming):
|
||||
``C:\Documents and Settings\<user>\Application Data\Foo Bar``
|
||||
Win 7 (roaming):
|
||||
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
||||
Win 7 (not roaming):
|
||||
``C:\Users\<user>\AppData\Local\Foo Bar``
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param app_name: the application name. This should be properly capitalized
|
||||
and can contain whitespace.
|
||||
:param roaming: controls if the folder should be roaming or not on Windows.
|
||||
Has no affect otherwise.
|
||||
:param force_posix: if this is set to `True` then on any POSIX system the
|
||||
folder will be stored in the home folder with a leading
|
||||
dot instead of the XDG config home or darwin's
|
||||
application support folder.
|
||||
"""
|
||||
if WIN:
|
||||
key = roaming and 'APPDATA' or 'LOCALAPPDATA'
|
||||
folder = os.environ.get(key)
|
||||
if folder is None:
|
||||
folder = os.path.expanduser('~')
|
||||
return os.path.join(folder, app_name)
|
||||
if force_posix:
|
||||
return os.path.join(os.path.expanduser('~/.' + _posixify(app_name)))
|
||||
if sys.platform == 'darwin':
|
||||
return os.path.join(os.path.expanduser(
|
||||
'~/Library/Application Support'), app_name)
|
||||
return os.path.join(
|
||||
os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
|
||||
_posixify(app_name))
|
130
docs/Makefile
Normal file
|
@ -0,0 +1,130 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Classy.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Classy.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Classy"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Classy"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
latexpdf: latex
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
BIN
docs/_static/click-small.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
docs/_static/click-small@2x.png
vendored
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/_static/click.png
vendored
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
docs/_static/click@2x.png
vendored
Normal file
After Width: | Height: | Size: 26 KiB |
13
docs/_templates/sidebarintro.html
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<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
Normal file
|
@ -0,0 +1,3 @@
|
|||
<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>
|
37
docs/_themes/LICENSE
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
Copyright (c) 2014 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Click and Click-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
31
docs/_themes/README
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
Flask Sphinx Styles
|
||||
===================
|
||||
|
||||
This repository contains sphinx styles for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
1. put this folder as _themes into your docs folder. Alternatively
|
||||
you can also use git submodules to check out the contents there.
|
||||
2. add this to your conf.py:
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'flask'
|
||||
|
||||
The following themes exist:
|
||||
|
||||
- 'flask' - the standard flask documentation theme for large
|
||||
projects
|
||||
- 'flask_small' - small one-page theme. Intended to be used by
|
||||
very small addon libraries for flask.
|
||||
|
||||
The following options exist for the flask_small theme:
|
||||
|
||||
[options]
|
||||
index_logo = '' filename of a picture in _static
|
||||
to be used as replacement for the
|
||||
h1 in the index.rst file.
|
||||
index_logo_height = 120px height of the index logo
|
||||
github_fork = '' repository name on github for the
|
||||
"fork me" badge
|
20
docs/_themes/click/layout.html
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
</div>
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endblock %}
|
19
docs/_themes/click/relations.html
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
403
docs/_themes/click/static/click.css_t
vendored
Normal file
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* click.css_t
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2014 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
@import url(http://fonts.googleapis.com/css?family=Ubuntu+Mono:400,400italic,700,700italic);
|
||||
@import url(http://fonts.googleapis.com/css?family=Open+Sans:300,400);
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Ubuntu Mono', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
|
||||
font-size: 15px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 300;
|
||||
margin: 20px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
color: #444;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Ubuntu Mono', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="text"] {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #5D2CD1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #7546E3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 400;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url(click.png) no-repeat center center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||
div.indexwrapper h1 {
|
||||
background: url(click@2x.png) no-repeat center center;
|
||||
background-size: 420px 175px;
|
||||
}
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Ubuntu Mono', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 7px 0 7px 30px;
|
||||
margin: 15px 0;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #5D2CD1;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #7546E3;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #5D2CD1;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #7546E3;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
4
docs/_themes/click/theme.conf
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = click.css
|
||||
pygments_style = tango
|
245
docs/advanced.rst
Normal file
|
@ -0,0 +1,245 @@
|
|||
Advanced Patterns
|
||||
=================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
In addition to common functionality that is implemented in the library
|
||||
itself, there are countless patterns that can be implemented by extending
|
||||
Click. This page should give some insight into what can be accomplished.
|
||||
|
||||
.. _aliases:
|
||||
|
||||
Command Aliases
|
||||
---------------
|
||||
|
||||
Many tools support aliases for commands. For instance, you can configure
|
||||
``git`` to accept ``git ci`` as alias for ``git commit``. Other tools
|
||||
also support auto-discovery for aliases by automatically shortening them.
|
||||
|
||||
Click does not support this out of the box, but it's very easy to customize
|
||||
the :class:`Group` or any other :class:`MultiCommand` to provide this
|
||||
functionality.
|
||||
|
||||
As explained in :ref:`custom-multi-commands`, a multi command can provide
|
||||
two methods: :meth:`~MultiCommand.list_commands` and
|
||||
:meth:`~MultiCommand.get_command`. In this particular case, you only need
|
||||
to override the latter as you generally don't want to enumerate the
|
||||
aliases on the help page in order to avoid confusion.
|
||||
|
||||
This following example implements a subclass of :class:`Group` that
|
||||
accepts a prefix for a command. If there were a command called ``push``,
|
||||
it would accept ``pus`` as an alias (so long as it was unique):
|
||||
|
||||
.. click:example::
|
||||
|
||||
class AliasedGroup(click.Group):
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
rv = click.Group.get_command(self, ctx, cmd_name)
|
||||
if rv is not None:
|
||||
return rv
|
||||
matches = [x for x in self.list_commands(ctx)
|
||||
if x.startswith(cmd_name)]
|
||||
if not matches:
|
||||
return None
|
||||
elif len(matches) == 1:
|
||||
return click.Group.get_command(self, ctx, matches[0])
|
||||
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
|
||||
|
||||
And it can then be used like this:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command(cls=AliasedGroup)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
def push():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
def pop():
|
||||
pass
|
||||
|
||||
Parameter Modifications
|
||||
-----------------------
|
||||
|
||||
Parameters (options and arguments) are forwarded to the command callbacks
|
||||
as you have seen. One common way to prevent a parameter from being passed
|
||||
to the callback is the `expose_value` argument to a parameter which hides
|
||||
the parameter entirely. The way this works is that the :class:`Context`
|
||||
object has a :attr:`~Context.params` attribute which is a dictionary of
|
||||
all parameters. Whatever is in that dictionary is being passed to the
|
||||
callbacks.
|
||||
|
||||
This can be used to make up addition parameters. Generally this pattern
|
||||
is not recommended but in some cases it can be useful. At the very least
|
||||
it's good to know that the system works this way.
|
||||
|
||||
.. click:example::
|
||||
|
||||
import urllib
|
||||
|
||||
def open_url(ctx, param, value):
|
||||
if value is not None:
|
||||
ctx.params['fp'] = urllib.urlopen(value)
|
||||
return value
|
||||
|
||||
@click.command()
|
||||
@click.option('--url', callback=open_url)
|
||||
def cli(url, fp=None):
|
||||
if fp is not None:
|
||||
click.echo('%s: %s' % (url, fp.code))
|
||||
|
||||
In this case the callback returns the URL unchanged but also passes a
|
||||
second ``fp`` value to the callback. What's more recommended is to pass
|
||||
the information in a wrapper however:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import urllib
|
||||
|
||||
class URL(object):
|
||||
|
||||
def __init__(self, url, fp):
|
||||
self.url = url
|
||||
self.fp = fp
|
||||
|
||||
def open_url(ctx, param, value):
|
||||
if value is not None:
|
||||
return URL(value, urllib.urlopen(value))
|
||||
|
||||
@click.command()
|
||||
@click.option('--url', callback=open_url)
|
||||
def cli(url):
|
||||
if url is not None:
|
||||
click.echo('%s: %s' % (url.url, url.fp.code))
|
||||
|
||||
|
||||
Token Normalization
|
||||
-------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Starting with Click 2.0, it's possible to provide a function that is used
|
||||
for normalizing tokens. Tokens are option names, choice values, or command
|
||||
values. This can be used to implement case insensitive options, for
|
||||
instance.
|
||||
|
||||
In order to use this feature, the context needs to be passed a function that
|
||||
performs the normalization of the token. For instance, you could have a
|
||||
function that converts the token to lowercase:
|
||||
|
||||
.. click:example::
|
||||
|
||||
CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.lower())
|
||||
|
||||
@click.command(context_settings=CONTEXT_SETTINGS)
|
||||
@click.option('--name', default='Pete')
|
||||
def cli(name):
|
||||
click.echo('Name: %s' % name)
|
||||
|
||||
And how it works on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='cli', args=['--NAME=Pete'])
|
||||
|
||||
Invoking Other Commands
|
||||
-----------------------
|
||||
|
||||
Sometimes, it might be interesting to invoke one command from another
|
||||
command. This is a pattern that is generally discouraged with Click, but
|
||||
possible nonetheless. For this, you can use the :func:`Context.invoke`
|
||||
or :func:`Context.forward` methods.
|
||||
|
||||
They work similarly, but the difference is that :func:`Context.invoke` merely
|
||||
invokes another command with the arguments you provide as a caller,
|
||||
whereas :func:`Context.forward` fills in the arguments from the current
|
||||
command. Both accept the command as the first argument and everything else
|
||||
is passed onwards as you would expect.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
cli = click.Group()
|
||||
|
||||
@cli.command()
|
||||
@click.option('--count', default=1)
|
||||
def test(count):
|
||||
click.echo('Count: %d' % count)
|
||||
|
||||
@cli.command()
|
||||
@click.option('--count', default=1)
|
||||
@click.pass_context
|
||||
def dist(ctx, count):
|
||||
ctx.forward(test)
|
||||
ctx.invoke(test, count=42)
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='cli', args=['dist'])
|
||||
|
||||
|
||||
.. _callback-evaluation-order:
|
||||
|
||||
Callback Evaluation Order
|
||||
-------------------------
|
||||
|
||||
Click works a bit differently than some other command line parsers in that
|
||||
it attempts to reconcile the order of arguments as defined by the
|
||||
programmer with the order of arguments as defined by the user before
|
||||
invoking any callbacks.
|
||||
|
||||
This is an important concept to understand when porting complex
|
||||
patterns to Click from optparse or other systems. A parameter
|
||||
callback invocation in optparse happens as part of the parsing step,
|
||||
whereas a callback invocation in Click happens after the parsing.
|
||||
|
||||
The main difference is that in optparse, callbacks are invoked with the raw
|
||||
value as it happens, whereas a callback in Click is invoked after the
|
||||
value has been fully converted.
|
||||
|
||||
Generally, the order of invocation is driven by the order in which the user
|
||||
provides the arguments to the script; if there is an option called ``--foo``
|
||||
and an option called ``--bar`` and the user calls it as ``--bar
|
||||
--foo``, then the callback for ``bar`` will fire before the one for ``foo``.
|
||||
|
||||
There are three exceptions to this rule which are important to know:
|
||||
|
||||
Eagerness:
|
||||
An option can be set to be "eager". All eager parameters are
|
||||
evaluated before all non-eager parameters, but again in the order as
|
||||
they were provided on the command line by the user.
|
||||
|
||||
This is important for parameters that execute and exit like ``--help``
|
||||
and ``--version``. Both are eager parameters, but whatever parameter
|
||||
comes first on the command line will win and exit the program.
|
||||
|
||||
Repeated parameters:
|
||||
If an option or argument is split up on the command line into multiple
|
||||
places because it is repeated -- for instance, ``--exclude foo --include
|
||||
baz --exclude bar`` -- the callback will fire based on the position of
|
||||
the first option. In this case, the callback will fire for
|
||||
``exclude`` and it will be passed both options (``foo`` and
|
||||
``bar``), then the callback for ``include`` will fire with ``baz``
|
||||
only.
|
||||
|
||||
Note that even if a parameter does not allow multiple versions, Click
|
||||
will still accept the position of the first, but it will ignore every
|
||||
value except the last. The reason for this is to allow composability
|
||||
through shell aliases that set defaults.
|
||||
|
||||
Missing parameters:
|
||||
If a parameter is not defined on the command line, the callback will
|
||||
still fire. This is different from how it works in optparse where
|
||||
undefined values do not fire the callback. Missing parameters fire
|
||||
their callbacks at the very end which makes it possible for them to
|
||||
default to values from a parameter that came before.
|
||||
|
||||
Most of the time you do not need to be concerned about any of this,
|
||||
but it is important to know how it works for some advanced cases.
|
169
docs/api.rst
Normal file
|
@ -0,0 +1,169 @@
|
|||
API
|
||||
===
|
||||
|
||||
.. module:: click
|
||||
|
||||
This part of the documentation lists the full API reference of all public
|
||||
classes and functions.
|
||||
|
||||
Decorators
|
||||
----------
|
||||
|
||||
.. autofunction:: command
|
||||
|
||||
.. autofunction:: group
|
||||
|
||||
.. autofunction:: argument
|
||||
|
||||
.. autofunction:: option
|
||||
|
||||
.. autofunction:: password_option
|
||||
|
||||
.. autofunction:: confirmation_option
|
||||
|
||||
.. autofunction:: version_option
|
||||
|
||||
.. autofunction:: help_option
|
||||
|
||||
.. autofunction:: pass_context
|
||||
|
||||
.. autofunction:: pass_obj
|
||||
|
||||
.. autofunction:: make_pass_decorator
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
.. autofunction:: echo
|
||||
|
||||
.. autofunction:: echo_via_pager
|
||||
|
||||
.. autofunction:: prompt
|
||||
|
||||
.. autofunction:: confirm
|
||||
|
||||
.. autofunction:: progressbar
|
||||
|
||||
.. autofunction:: clear
|
||||
|
||||
.. autofunction:: style
|
||||
|
||||
.. autofunction:: unstyle
|
||||
|
||||
.. autofunction:: secho
|
||||
|
||||
.. autofunction:: edit
|
||||
|
||||
.. autofunction:: launch
|
||||
|
||||
.. autofunction:: getchar
|
||||
|
||||
.. autofunction:: pause
|
||||
|
||||
.. autofunction:: get_terminal_size
|
||||
|
||||
.. autofunction:: get_binary_stream
|
||||
|
||||
.. autofunction:: get_text_stream
|
||||
|
||||
.. autofunction:: open_file
|
||||
|
||||
.. autofunction:: get_app_dir
|
||||
|
||||
.. autofunction:: format_filename
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. autoclass:: BaseCommand
|
||||
:members:
|
||||
|
||||
.. autoclass:: Command
|
||||
:members:
|
||||
|
||||
.. autoclass:: MultiCommand
|
||||
:members:
|
||||
|
||||
.. autoclass:: Group
|
||||
:members:
|
||||
|
||||
.. autoclass:: CommandCollection
|
||||
:members:
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
.. autoclass:: Parameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: Option
|
||||
|
||||
.. autoclass:: Argument
|
||||
|
||||
Context
|
||||
-------
|
||||
|
||||
.. autoclass:: Context
|
||||
:members:
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
.. autodata:: STRING
|
||||
|
||||
.. autodata:: INT
|
||||
|
||||
.. autodata:: FLOAT
|
||||
|
||||
.. autodata:: BOOL
|
||||
|
||||
.. autodata:: UUID
|
||||
|
||||
.. autoclass:: File
|
||||
|
||||
.. autoclass:: Path
|
||||
|
||||
.. autoclass:: Choice
|
||||
|
||||
.. autoclass:: IntRange
|
||||
|
||||
.. autoclass:: ParamType
|
||||
:members:
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
.. autoexception:: ClickException
|
||||
|
||||
.. autoexception:: Abort
|
||||
|
||||
.. autoexception:: UsageError
|
||||
|
||||
.. autoexception:: BadParameter
|
||||
|
||||
.. autoexception:: FileError
|
||||
|
||||
Formatting
|
||||
----------
|
||||
|
||||
.. autoclass:: HelpFormatter
|
||||
:members:
|
||||
|
||||
.. autofunction:: wrap_text
|
||||
|
||||
Parsing
|
||||
-------
|
||||
|
||||
.. autoclass:: OptionParser
|
||||
:members:
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
.. currentmodule:: click.testing
|
||||
|
||||
.. autoclass:: CliRunner
|
||||
:members:
|
||||
|
||||
.. autoclass:: Result
|
||||
:members:
|
245
docs/arguments.rst
Normal file
|
@ -0,0 +1,245 @@
|
|||
.. _arguments:
|
||||
|
||||
Arguments
|
||||
=========
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Arguments work similarly to :ref:`options <options>` but are positional.
|
||||
They also only support a subset of the features of options due to their
|
||||
syntactical nature. Click will also not attempt to document arguments for
|
||||
you and wants you to document them manually in order to avoid ugly help
|
||||
pages.
|
||||
|
||||
Basic Arguments
|
||||
---------------
|
||||
|
||||
The most basic option is a simple string argument of one value. If no
|
||||
type is provided, the type of the default value is used, and if no default
|
||||
value is provided, the type is assumed to be :data:`STRING`.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('filename')
|
||||
def touch(filename):
|
||||
click.echo(filename)
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(touch, args=['foo.txt'])
|
||||
|
||||
Variadic Arguments
|
||||
------------------
|
||||
|
||||
The second most common version is variadic arguments where a specific (or
|
||||
unlimited) number of arguments is accepted. This can be controlled with
|
||||
the ``nargs`` parameter. If it is set to ``-1``, then an unlimited number
|
||||
of arguments is accepted.
|
||||
|
||||
The value is then passed as a tuple. Note that only one argument can be
|
||||
set to ``nargs=-1``, as it will eat up all arguments.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('src', nargs=-1)
|
||||
@click.argument('dst', nargs=1)
|
||||
def copy(src, dst):
|
||||
for fn in src:
|
||||
click.echo('move %s to folder %s' % (fn, dst))
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(copy, args=['foo.txt', 'bar.txt', 'my_folder'])
|
||||
|
||||
Note that this is not how you would write this application. The reason
|
||||
for this is that in this particular example the arguments are defined as
|
||||
strings. Filenames, however, are not strings! They might be on certain
|
||||
operating systems, but not necessarily on all. For better ways to write
|
||||
this, see the next sections.
|
||||
|
||||
.. admonition:: Note on Non-Empty Variadic Arguments
|
||||
|
||||
If you come from ``argparse``, you might be missing support for setting
|
||||
``nargs`` to ``+`` to indicate that at least one argument is required.
|
||||
|
||||
This is supported by setting ``required=True``. However, this should
|
||||
not be used if you can avoid it as we believe scripts should gracefully
|
||||
degrade into becoming noops if a variadic argument is empty. The
|
||||
reason for this is that very often, scripts are invoked with wildcard
|
||||
inputs from the command line and they should not error out if the
|
||||
wildcard is empty.
|
||||
|
||||
.. _file-args:
|
||||
|
||||
File Arguments
|
||||
--------------
|
||||
|
||||
Since all the examples have already worked with filenames, it makes sense
|
||||
to explain how to deal with files properly. Command line tools are more
|
||||
fun if they work with files the Unix way, which is to accept ``-`` as a
|
||||
special file that refers to stdin/stdout.
|
||||
|
||||
Click supports this through the :class:`click.File` type which
|
||||
intelligently handles files for you. It also deals with Unicode and bytes
|
||||
correctly for all versions of Python so your script stays very portable.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('input', type=click.File('rb'))
|
||||
@click.argument('output', type=click.File('wb'))
|
||||
def inout(input, output):
|
||||
while True:
|
||||
chunk = input.read(1024)
|
||||
if not chunk:
|
||||
break
|
||||
output.write(chunk)
|
||||
|
||||
And what it does:
|
||||
|
||||
.. click:run::
|
||||
|
||||
with isolated_filesystem():
|
||||
invoke(inout, args=['-', 'hello.txt'], input=['hello'],
|
||||
terminate_input=True)
|
||||
invoke(inout, args=['hello.txt', '-'])
|
||||
|
||||
File Path Arguments
|
||||
-------------------
|
||||
|
||||
In the previous example, the files were opened immediately. But what if
|
||||
we just want the filename? The naïve way is to use the default string
|
||||
argument type. However, remember that Click is Unicode-based, so the string
|
||||
will always be a Unicode value. Unfortunately, filenames can be Unicode or
|
||||
bytes depending on which operating system is being used. As such, the type
|
||||
is insufficient.
|
||||
|
||||
Instead, you should be using the :class:`Path` type, which automatically
|
||||
handles this ambiguity. Not only will it return either bytes or Unicode
|
||||
depending on what makes more sense, but it will also be able to do some
|
||||
basic checks for you such as existence checks.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('f', type=click.Path(exists=True))
|
||||
def touch(f):
|
||||
click.echo(click.format_filename(f))
|
||||
|
||||
And what it does:
|
||||
|
||||
.. click:run::
|
||||
|
||||
with isolated_filesystem():
|
||||
with open('hello.txt', 'w') as f:
|
||||
f.write('Hello World!\n')
|
||||
invoke(touch, args=['hello.txt'])
|
||||
println()
|
||||
invoke(touch, args=['missing.txt'])
|
||||
|
||||
|
||||
File Opening Safety
|
||||
-------------------
|
||||
|
||||
The :class:`FileType` type has one problem it needs to deal with, and that
|
||||
is to decide when to open a file. The default behavior is to be
|
||||
"intelligent" about it. What this means is that it will open stdin/stdout
|
||||
and files opened for reading immediately. This will give the user direct
|
||||
feedback when a file cannot be opened, but it will only open files
|
||||
for writing the first time an IO operation is performed by automatically
|
||||
wrapping the file in a special wrapper.
|
||||
|
||||
This behavior can be forced by passing ``lazy=True`` or ``lazy=False`` to
|
||||
the constructor. If the file is opened lazily, it will fail its first IO
|
||||
operation by raising an :exc:`FileError`.
|
||||
|
||||
Since files opened for writing will typically immediately empty the file,
|
||||
the lazy mode should only be disabled if the developer is absolutely sure
|
||||
that this is intended behavior.
|
||||
|
||||
Forcing lazy mode is also very useful to avoid resource handling
|
||||
confusion. If a file is opened in lazy mode, it will receive a
|
||||
``close_intelligently`` method that can help figure out if the file
|
||||
needs closing or not. This is not needed for parameters, but is
|
||||
necessary for manually prompting with the :func:`prompt` function as you
|
||||
do not know if a stream like stdout was opened (which was already open
|
||||
before) or a real file that needs closing.
|
||||
|
||||
Starting with Click 2.0, it is also possible to open files in atomic mode by
|
||||
passing ``atomic=True``. In atomic mode, all writes go into a separate
|
||||
file in the same folder, and upon completion, the file will be moved over to
|
||||
the original location. This is useful if a file regularly read by other
|
||||
users is modified.
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
|
||||
Like options, arguments can also grab values from an environment variable.
|
||||
Unlike options, however, this is only supported for explicitly named
|
||||
environment variables.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('src', envvar='SRC', type=click.File('r'))
|
||||
def echo(src):
|
||||
click.echo(src.read())
|
||||
|
||||
And from the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
with isolated_filesystem():
|
||||
with open('hello.txt', 'w') as f:
|
||||
f.write('Hello World!')
|
||||
invoke(echo, env={'SRC': 'hello.txt'})
|
||||
|
||||
In that case, it can also be a list of different environment variables
|
||||
where the first one is picked.
|
||||
|
||||
Generally, this feature is not recommended because it can cause the user
|
||||
a lot of confusion.
|
||||
|
||||
Argument-Like Options
|
||||
---------------------
|
||||
|
||||
Sometimes, you want to process arguments that look like options. For
|
||||
instance, imagine you have a file named ``-foo.txt``. If you pass this as
|
||||
an argument in this manner, Click will treat it as an option.
|
||||
|
||||
To solve this, Click does what any POSIX style command line script does,
|
||||
and that is to accept the string ``--`` as a separator for options and
|
||||
arguments. After the ``--`` marker, all further parameters are accepted as
|
||||
arguments.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@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'])
|
69
docs/bashcomplete.rst
Normal file
|
@ -0,0 +1,69 @@
|
|||
Bash Complete
|
||||
=============
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
As of Click 2.0, there is built-in support for Bash completion for
|
||||
any Click script. There are certain restrictions on when this completion
|
||||
is available, but for the most part it should just work.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Bash completion is only available if a script has been installed properly,
|
||||
and not executed through the ``python`` command. For information about
|
||||
how to do that, see :ref:`setuptools-integration`. Also, Click currently
|
||||
only supports completion for Bash.
|
||||
|
||||
Currently, Bash completion is an internal feature that is not customizable.
|
||||
This might be relaxed in future versions.
|
||||
|
||||
What it Completes
|
||||
-----------------
|
||||
|
||||
Generally, the Bash completion support will complete subcommands and
|
||||
parameters. Subcommands are always listed whereas parameters only if at
|
||||
least a dash has been provided. Example::
|
||||
|
||||
$ repo <TAB><TAB>
|
||||
clone commit copy delete setuser
|
||||
$ repo clone -<TAB><TAB>
|
||||
--deep --help --rev --shallow -r
|
||||
|
||||
Activation
|
||||
----------
|
||||
|
||||
In order to activate Bash completion, you need to inform Bash that
|
||||
completion is available for your script, and how. Any Click application
|
||||
automatically provides support for that. The general way this works is
|
||||
through a magic environment variable called ``_<PROG_NAME>_COMPLETE``,
|
||||
where ``<PROG_NAME>`` is your application executable name in uppercase
|
||||
with dashes replaced by underscores.
|
||||
|
||||
If your tool is called ``foo-bar``, then the magic variable is called
|
||||
``_FOO_BAR_COMPLETE``. By exporting it with the ``source`` value it will
|
||||
spit out the activation script which can be trivally activated.
|
||||
|
||||
For instance, to enable Bash completion for your ``foo-bar`` script, this
|
||||
is what you would need to put into your ``.bashrc``::
|
||||
|
||||
eval "$(_FOO_BAR_COMPLETE=source foo-bar)"
|
||||
|
||||
From this point onwards, your script will have Bash completion enabled.
|
||||
|
||||
Activation Script
|
||||
-----------------
|
||||
|
||||
The above activation example will always invoke your application on
|
||||
startup. This might be slowing down the shell activation time
|
||||
significantly if you have many applications. Alternatively, you could also
|
||||
ship a file with the contents of that, which is what Git and other systems
|
||||
are doing.
|
||||
|
||||
This can be easily accomplished::
|
||||
|
||||
_FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh
|
||||
|
||||
And then you would put this into your bashrc instead::
|
||||
|
||||
. /path/to/foo-bar-complete.sh
|
3
docs/changelog.rst
Normal file
|
@ -0,0 +1,3 @@
|
|||
.. currentmodule:: click
|
||||
|
||||
.. include:: ../CHANGES
|
255
docs/clickdoctools.py
Normal file
|
@ -0,0 +1,255 @@
|
|||
import os
|
||||
import sys
|
||||
import click
|
||||
import shutil
|
||||
import tempfile
|
||||
import contextlib
|
||||
|
||||
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 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):
|
||||
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)
|
503
docs/commands.rst
Normal file
|
@ -0,0 +1,503 @@
|
|||
Commands and Groups
|
||||
===================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
The most important feature of Click is the concept of arbitrarily nesting
|
||||
command line utilities. This is implemented through the :class:`Command`
|
||||
and :class:`Group` (actually :class:`MultiCommand`).
|
||||
|
||||
Callback Invocation
|
||||
-------------------
|
||||
|
||||
For a regular command, the callback is executed whenever the command runs.
|
||||
If the script is the only command, it will always fire (unless a parameter
|
||||
callback prevents it. This for instance happens if someone passes
|
||||
``--help`` to the script).
|
||||
|
||||
For groups and multi commands, the situation looks different. In this case,
|
||||
the callback fires whenever a subcommand fires (unless this behavior is
|
||||
changed). What this means in practice is that an outer command runs
|
||||
when an inner command runs:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group()
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
def cli(debug):
|
||||
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
|
||||
|
||||
@cli.command()
|
||||
def sync():
|
||||
click.echo('Synching')
|
||||
|
||||
Here is what this looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='tool.py')
|
||||
println()
|
||||
invoke(cli, prog_name='tool.py', args=['--debug', 'sync'])
|
||||
|
||||
Passing Parameters
|
||||
------------------
|
||||
|
||||
Click strictly separates parameters between commands and subcommands. What this
|
||||
means is that options and arguments for a specific command have to be specified
|
||||
*after* the command name itself, but *before* any other command names.
|
||||
|
||||
This behavior is already observable with the predefined ``--help`` option.
|
||||
Suppose we have a program called ``tool.py``, containing a subcommand called
|
||||
``sub``.
|
||||
|
||||
- ``tool.py --help`` will return the help for the whole program (listing
|
||||
subcommands).
|
||||
|
||||
- ``tool.py sub --help`` will return the help for the ``sub`` subcommand.
|
||||
|
||||
- But ``tool.py --help sub`` will treat ``--help`` as an argument for the main
|
||||
program. Click then invokes the callback for ``--help``, which prints the
|
||||
help and aborts the program before click can process the subcommand.
|
||||
|
||||
Nested Handling and Contexts
|
||||
----------------------------
|
||||
|
||||
As you can see from the earlier example, the basic command group accepts a
|
||||
debug argument which is passed to its callback, but not to the sync
|
||||
command itself. The sync command only accepts its own arguments.
|
||||
|
||||
This allows tools to act completely independent of each other, but how
|
||||
does one command talk to a nested one? The answer to this is the
|
||||
:class:`Context`.
|
||||
|
||||
Each time a command is invoked, a new context is created and linked with the
|
||||
parent context. Normally, you can't see these contexts, but they are
|
||||
there. Contexts are passed to parameter callbacks together with the
|
||||
value automatically. Commands can also ask for the context to be passed
|
||||
by marking themselves with the :func:`pass_context` decorator. In that
|
||||
case, the context is passed as first argument.
|
||||
|
||||
The context can also carry a program specified object that can be
|
||||
used for the program's purposes. What this means is that you can build a
|
||||
script like this:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group()
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
@click.pass_context
|
||||
def cli(ctx, debug):
|
||||
ctx.obj['DEBUG'] = debug
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def sync(ctx):
|
||||
click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli(obj={})
|
||||
|
||||
If the object is provided, each context will pass the object onwards to
|
||||
its children, but at any level a context's object can be overridden. To
|
||||
reach to a parent, ``context.parent`` can be used.
|
||||
|
||||
In addition to that, instead of passing an object down, nothing stops the
|
||||
application from modifying global state. For instance, you could just flip
|
||||
a global ``DEBUG`` variable and be done with it.
|
||||
|
||||
Decorating Commands
|
||||
-------------------
|
||||
|
||||
As you have seen in the earlier example, a decorator can change how a
|
||||
command is invoked. What actually happens behind the scenes is that
|
||||
callbacks are always invoked through the :meth:`Context.invoke` method
|
||||
which automatically invokes a command correctly (by either passing the
|
||||
context or not).
|
||||
|
||||
This is very useful when you want to write custom decorators. For
|
||||
instance, a common pattern would be to configure an object representing
|
||||
state and then storing it on the context and then to use a custom
|
||||
decorator to find the most recent object of this sort and pass it as first
|
||||
argument.
|
||||
|
||||
For instance, the :func:`pass_obj` decorator can be implemented like this:
|
||||
|
||||
.. click:example::
|
||||
|
||||
from functools import update_wrapper
|
||||
|
||||
def pass_obj(f):
|
||||
@click.pass_context
|
||||
def new_func(ctx, *args, **kwargs):
|
||||
return ctx.invoke(f, ctx.obj, *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
The :meth:`Context.invoke` command will automatically invoke the function
|
||||
in the correct way, so the function will either be called with ``f(ctx,
|
||||
obj)`` or ``f(obj)`` depending on whether or not it itself is decorated with
|
||||
:func:`with_context`.
|
||||
|
||||
This is a very powerful context that can be used to build very complex
|
||||
nested applications; see :ref:`complex-guide` for more information.
|
||||
|
||||
|
||||
Group Invocation Without Command
|
||||
--------------------------------
|
||||
|
||||
By default, a group or multi command is not invoked unless a subcommand is
|
||||
passed. In fact, not providing a command automatically passes ``--help``
|
||||
by default. This behavior can be changed by passing
|
||||
``invoke_without_command=True`` to a group. In that case, the callback is
|
||||
always invoked instead of showing the help page. The context object also
|
||||
includes information about whether or not the invocation would go to a
|
||||
subcommand.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
if ctx.invoked_subcommand is None:
|
||||
click.echo('I was invoked without subcommand')
|
||||
else:
|
||||
click.echo('I am about to invoke %s' % ctx.invoked_subcommand)
|
||||
|
||||
@cli.command()
|
||||
def sync():
|
||||
click.echo('The subcommand')
|
||||
|
||||
And how it works in practice:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='tool', args=[])
|
||||
invoke(cli, prog_name='tool', args=['sync'])
|
||||
|
||||
.. _custom-multi-commands:
|
||||
|
||||
Custom Multi Commands
|
||||
---------------------
|
||||
|
||||
In addition to using :func:`click.group`, you can also build your own
|
||||
custom multi commands. This is useful when you want to support commands
|
||||
being loaded lazily from plugins.
|
||||
|
||||
A custom multi command just needs to implement a list and load method:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import click
|
||||
import os
|
||||
|
||||
plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')
|
||||
|
||||
class MyCLI(click.MultiCommand):
|
||||
|
||||
def list_commands(self, ctx):
|
||||
rv = []
|
||||
for filename in os.listdir(plugin_folder):
|
||||
if filename.endswith('.py'):
|
||||
rv.append(filename[:-3])
|
||||
rv.sort()
|
||||
return rv
|
||||
|
||||
def get_command(self, ctx, name):
|
||||
ns = {}
|
||||
fn = os.path.join(plugin_folder, name + '.py')
|
||||
with open(fn) as f:
|
||||
code = compile(f.read(), fn, 'exec')
|
||||
eval(code, ns, ns)
|
||||
return ns['cli']
|
||||
|
||||
cli = MyCLI(help='This tool\'s subcommands are loaded from a '
|
||||
'plugin folder dynamically.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
|
||||
These custom classes can also be used with decorators:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command(cls=MyCLI)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
Merging Multi Commands
|
||||
----------------------
|
||||
|
||||
In addition to implementing custom multi commands, it can also be
|
||||
interesting to merge multiple together into one script. While this is
|
||||
generally not as recommended as it nests one below the other, the merging
|
||||
approach can be useful in some circumstances for a nicer shell experience.
|
||||
|
||||
The default implementation for such a merging system is the
|
||||
:class:`CommandCollection` class. It accepts a list of other multi
|
||||
commands and makes the commands available on the same level.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
def cli1():
|
||||
pass
|
||||
|
||||
@cli1.command()
|
||||
def cmd1():
|
||||
"""Command on cli1"""
|
||||
|
||||
@click.group()
|
||||
def cli2():
|
||||
pass
|
||||
|
||||
@cli2.command()
|
||||
def cmd2():
|
||||
"""Command on cli2"""
|
||||
|
||||
cli = click.CommandCollection(sources=[cli1, cli2])
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='cli', args=['--help'])
|
||||
|
||||
In case a command exists in more than one source, the first source wins.
|
||||
|
||||
|
||||
Multi Command Chaining
|
||||
----------------------
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
Sometimes it is useful to be allowed to invoke more than one subcommand in
|
||||
one go. For instance if you have installed a setuptools package before
|
||||
ouy might be familiar with the ``setup.py sdist bdist_wheel upload``
|
||||
command chain which invokes ``dist`` before ``bdist_wheel`` before
|
||||
``upload``. Starting with Click 3.0 this is very simple to implement.
|
||||
All you have to do is to pass ``chain=True`` to your multicommand:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group(chain=True)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command('sdist')
|
||||
def sdist():
|
||||
click.echo('sdist called')
|
||||
|
||||
|
||||
@cli.command('bdist_wheel')
|
||||
def bdist_wheel():
|
||||
click.echo('bdist_wheel called')
|
||||
|
||||
Now you can invoke it like this:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel'])
|
||||
|
||||
When using multi command chaining you can only have one command (the last)
|
||||
use ``nargs=-1`` on an argument. Other than that there are no
|
||||
restrictions on how they work. They can accept options and arguments as
|
||||
normal.
|
||||
|
||||
Another note: the :attr:`Context.invoked_subcommand` attribute is a bit
|
||||
useless for multi commands as it will give ``'*'`` as value if more than
|
||||
one command is invoked. This is necessary because the handling of
|
||||
subcommands happens one after another so the exact subcommands that will
|
||||
be handled are not yet available when the callback fires.
|
||||
|
||||
|
||||
Multi Command Pipelines
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
A very common usecase of multi command chaining is to have one command
|
||||
process the result of the previous command. There are various ways in
|
||||
which this can be facilitated. The most obvious way is to store a value
|
||||
on the context object and process it from function to function. This
|
||||
works by decorating a function with :func:`pass_context` after which the
|
||||
context object is provided and a subcommand can store it's data there.
|
||||
|
||||
Another way to accomplish this is to setup pipelines by returning
|
||||
processing functions. Think of it like this: when a subcommand gets
|
||||
invoked it processes all of it's parameters and comes up with a plan of
|
||||
how to do it's processing. At that point it then returns a processing
|
||||
function and returns.
|
||||
|
||||
Where do the returned functions go? The chained multicommand can register
|
||||
a callback with :meth:`MultiCommand.resultcallback` that goes over all
|
||||
these functions and then invoke them.
|
||||
|
||||
To make this a bit more concrete consider this example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group(chain=True, invoke_without_command=True)
|
||||
@click.option('-i', '--input', type=click.File('r'))
|
||||
def cli(input):
|
||||
pass
|
||||
|
||||
@cli.resultcallback()
|
||||
def process_pipeline(processors, input):
|
||||
iterator = (x.rstrip('\r\n') for x in input)
|
||||
for processor in processors:
|
||||
iterator = processor(iterator)
|
||||
for item in iterator:
|
||||
click.echo(item)
|
||||
|
||||
@cli.command('uppercase')
|
||||
def make_uppercase():
|
||||
def processor(iterator):
|
||||
for line in iterator:
|
||||
yield line.upper()
|
||||
return processor
|
||||
|
||||
@cli.command('lowercase')
|
||||
def make_lowercase():
|
||||
def processor(iterator):
|
||||
for line in iterator:
|
||||
yield line.lower()
|
||||
return processor
|
||||
|
||||
@cli.command('strip')
|
||||
def make_strip():
|
||||
def processor(iterator):
|
||||
for line in iterator:
|
||||
yield line.strip()
|
||||
return processor
|
||||
|
||||
That's a lot in one go, so let's go through it step by step.
|
||||
|
||||
1. The first thing is to make a :func:`group` that is chainable. In
|
||||
addition to that we also instruct Click to invoke even if no
|
||||
subcommand is defined. If this would not be done, then invoking an
|
||||
empty pipeline would produce the help page instead of running the
|
||||
result callbacks.
|
||||
2. The next thing we do is to register a result callback on our group.
|
||||
This callback will be invoked with an argument which is the list of
|
||||
all return values of all subcommands and then the same keyword
|
||||
parameters as our group itself. This means we can access the input
|
||||
file easily there without having to use the context object.
|
||||
3. In this result callback we create an iterator of all the lines in the
|
||||
input file and then pass this iterator through all the returned
|
||||
callbacks from all subcommands and finally we print all lines to
|
||||
stdout.
|
||||
|
||||
After that point we can register as many subcommands as we want and each
|
||||
subcommand can return a processor function to modify the stream of lines.
|
||||
|
||||
One important thing of note is that Click shuts down the context after
|
||||
each callback has been run. This means that for instance file types
|
||||
cannot be accessed in the `processor` functions as the files will already
|
||||
be closed there. This limitation is unlikely to change because it would
|
||||
make resource handling much more complicated. For such it's recommended
|
||||
to not use the file type and manually open the file through
|
||||
:func:`open_file`.
|
||||
|
||||
For a more complex example that also improves upon handling of the
|
||||
pipelines have a look at the `imagepipe multi command chaining demo
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/imagepipe>`__ in
|
||||
the Click repository. It implements a pipeline based image editing tool
|
||||
that has a nice internal structure for the pipelines.
|
||||
|
||||
|
||||
Overriding Defaults
|
||||
-------------------
|
||||
|
||||
By default, the default value for a parameter is pulled from the
|
||||
``default`` flag that is provided when it's defined, but that's not the
|
||||
only place defaults can be loaded from. The other place is the
|
||||
:attr:`Context.default_map` (a dictionary) on the context. This allows
|
||||
defaults to be loaded from a configuration file to override the regular
|
||||
defaults.
|
||||
|
||||
This is useful if you plug in some commands from another package but
|
||||
you're not satisfied with the defaults.
|
||||
|
||||
The default map can be nested arbitrarily for each subcommand and
|
||||
provided when the script is invoked. Alternatively, it can also be
|
||||
overriden at any point by commands. For instance, a top-level command could
|
||||
load the defaults from a configuration file.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--port', default=8000)
|
||||
def runserver(port):
|
||||
click.echo('Serving on http://127.0.0.1:%d/' % port)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli(default_map={
|
||||
'runserver': {
|
||||
'port': 5000
|
||||
}
|
||||
})
|
||||
|
||||
And in action:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='cli', args=['runserver'], default_map={
|
||||
'runserver': {
|
||||
'port': 5000
|
||||
}
|
||||
})
|
||||
|
||||
Context Defaults
|
||||
----------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Starting with Click 2.0 you can override defaults for contexts not just
|
||||
when calling your script, but also in the decorator that declares a
|
||||
command. For instance given the previous example which defines a custom
|
||||
``default_map`` this can also be accomplished in the decorator now.
|
||||
|
||||
This example does the same as the previous example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import click
|
||||
|
||||
CONTEXT_SETTINGS = dict(
|
||||
default_map={'runserver': {'port': 5000}}
|
||||
)
|
||||
|
||||
@click.group(context_settings=CONTEXT_SETTINGS)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--port', default=8000)
|
||||
def runserver(port):
|
||||
click.echo('Serving on http://127.0.0.1:%d/' % port)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
|
||||
And again the example in action:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='cli', args=['runserver'])
|
219
docs/complex.rst
Normal file
|
@ -0,0 +1,219 @@
|
|||
.. _complex-guide:
|
||||
|
||||
Complex Applications
|
||||
====================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Click is designed to assist with the creation of complex and simple CLI tools
|
||||
alike. However, the power of its design is the ability to arbitrarily nest
|
||||
systems together. For instance, if you have ever used Django, you will
|
||||
have realized that it provides a command line utility, but so does Celery.
|
||||
When using Celery with Django, there are two tools that need to interact with
|
||||
each other and be cross-configured.
|
||||
|
||||
In a theoretical world of two separate Click command line utilities, they
|
||||
could solve this problem by nesting one inside the other. For instance, the
|
||||
web framework could also load the commands for the message queue framework.
|
||||
|
||||
Basic Concepts
|
||||
--------------
|
||||
|
||||
To understand how this works, you need to understand two concepts: contexts
|
||||
and the calling convention.
|
||||
|
||||
Contexts
|
||||
````````
|
||||
|
||||
Whenever a Click command is executed, a :class:`Context` object is created
|
||||
which holds state for this particular invocation. It remembers parsed
|
||||
parameters, what command created it, which resources need to be cleaned up
|
||||
at the end of the function, and so forth. It can also optionally hold an
|
||||
application-defined object.
|
||||
|
||||
Context objects build a linked list until they hit the top one. Each context
|
||||
is linked to a parent context. This allows a command to work below
|
||||
another command and store its own information there without having to be
|
||||
afraid of altering up the state of the parent command.
|
||||
|
||||
Because the parent data is available, however, it is possible to navigate to
|
||||
it if needed.
|
||||
|
||||
Most of the time, you do not see the context object, but when writing more
|
||||
complex applications it comes in handy. This brings us to the next point.
|
||||
|
||||
Calling Convention
|
||||
``````````````````
|
||||
|
||||
When a Click command callback is executed, it's passed all the non-hidden
|
||||
parameters as keyword arguments. Notably absent is the context. However,
|
||||
a callback can opt into being passed to the context object by marking itself
|
||||
with :func:`pass_context`.
|
||||
|
||||
So how do you invoke a command callback if you don't know if it should
|
||||
receive the context or not? The answer is that the context itself
|
||||
provides a helper function (:meth:`Context.invoke`) which can do this for
|
||||
you. It accepts the callback as first argument and then invokes the
|
||||
function correctly.
|
||||
|
||||
Building a Git Clone
|
||||
--------------------
|
||||
|
||||
In this example, we want to build a command line tool that resembles a
|
||||
version control system. Systems like Git usually provide one
|
||||
over-arching command that already accepts some parameters and
|
||||
configuration, and then have extra subcommands that do other things.
|
||||
|
||||
The Root Command
|
||||
````````````````
|
||||
|
||||
At the top level, we need a group that can hold all our commands. In this
|
||||
case, we use the basic :func:`click.group` which allows us to register
|
||||
other Click commands below it.
|
||||
|
||||
For this command, we also want to accept some parameters that configure the
|
||||
state of our tool:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import os
|
||||
import click
|
||||
|
||||
|
||||
class Repo(object):
|
||||
def __init__(self, home=None, debug=False):
|
||||
self.home = os.path.abspath(home or '.')
|
||||
self.debug = debug
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option('--repo-home', envvar='REPO_HOME', default='.repo')
|
||||
@click.option('--debug/--no-debug', default=False,
|
||||
envvar='REPO_DEBUG')
|
||||
@click.pass_context
|
||||
def cli(ctx, repo_home, debug):
|
||||
ctx.obj = Repo(repo_home, debug)
|
||||
|
||||
|
||||
Let's understand what this does. We create a group command which can
|
||||
have subcommands. When it is invoked, it will create an instance of a
|
||||
``Repo`` class. This holds the state for our command line tool. In this
|
||||
case, it just remembers some parameters, but at this point it could also
|
||||
start loading configuration files and so on.
|
||||
|
||||
This state object is then remembered by the context as :attr:`~Context.obj`.
|
||||
This is a special attribute where commands are supposed to remember what
|
||||
they need to pass on to their children.
|
||||
|
||||
In order for this to work, we need to mark our function with
|
||||
:func:`pass_context`, because otherwise, the context object would be
|
||||
entirely hidden from us.
|
||||
|
||||
The First Child Command
|
||||
```````````````````````
|
||||
|
||||
Let's add our first child command to it, the clone command:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@cli.command()
|
||||
@click.argument('src')
|
||||
@click.argument('dest', required=False)
|
||||
def clone(src, dest):
|
||||
pass
|
||||
|
||||
So now we have a clone command, but how do we get access to the repo? As
|
||||
you can imagine, one way is to use the :func:`pass_context` function which
|
||||
again will make our callback also get the context passed on which we
|
||||
memorized the repo. However, there is a second version of this decorator
|
||||
called :func:`pass_obj` which will just pass the stored object, (in our case
|
||||
the repo):
|
||||
|
||||
.. click:example::
|
||||
|
||||
@cli.command()
|
||||
@click.argument('src')
|
||||
@click.argument('dest', required=False)
|
||||
@click.pass_obj
|
||||
def clone(repo, src, dest):
|
||||
pass
|
||||
|
||||
Interleaved Commands
|
||||
````````````````````
|
||||
|
||||
While not relevant for the particular program we want to build, there is
|
||||
also quite good support for interleaving systems. Imagine for instance that
|
||||
there was a super cool plugin for our version control system that needed a
|
||||
lot of configuration and wanted to store its own configuration as
|
||||
:attr:`~Context.obj`. If we would then attach another command below that,
|
||||
we would all of a sudden get the plugin configuration instead of our repo
|
||||
object.
|
||||
|
||||
One obvious way to remedy this is to store a reference to the repo in the
|
||||
plugin, but then a command needs to be aware that it's attached below such a
|
||||
plugin.
|
||||
|
||||
There is a much better system that can built by taking advantage of the linked
|
||||
nature of contexts. We know that the plugin context is linked to the context
|
||||
that created our repo. Because of that, we can start a search for the last
|
||||
level where the object stored by the context was a repo.
|
||||
|
||||
Built-in support for this is provided by the :func:`make_pass_decorator`
|
||||
factory, which will create decorators for us that find objects (it
|
||||
internally calls into :meth:`Context.find_object`). In our case, we
|
||||
know that we want to find the closest ``Repo`` object, so let's make a
|
||||
decorator for this:
|
||||
|
||||
.. click:example::
|
||||
|
||||
pass_repo = click.make_pass_decorator(Repo)
|
||||
|
||||
If we now use ``pass_repo`` instead of ``pass_obj``, we will always get a
|
||||
repo instead of something else:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@cli.command()
|
||||
@click.argument('src')
|
||||
@click.argument('dest', required=False)
|
||||
@pass_repo
|
||||
def clone(repo, src, dest):
|
||||
pass
|
||||
|
||||
Ensuring Object Creation
|
||||
````````````````````````
|
||||
|
||||
The above example only works if there was an outer command that created a
|
||||
``Repo`` object and stored it in the context. For some more advanced use
|
||||
cases, this might become a problem. The default behavior of
|
||||
:func:`make_pass_decorator` is to call :meth:`Context.find_object`
|
||||
which will find the object. If it can't find the object, it will raise an
|
||||
error. The alternative behavior is to use :meth:`Context.ensure_object`
|
||||
which will find the object, and if it cannot find it, will create one and
|
||||
store it in the innermost context. This behavior can also be enabled for
|
||||
:func:`make_pass_decorator` by passing ``ensure=True``:
|
||||
|
||||
.. click:example::
|
||||
|
||||
pass_repo = click.make_pass_decorator(Repo, ensure=True)
|
||||
|
||||
In this case, the innermost context gets an object created if it is
|
||||
missing. This might replace objects being placed there earlier. In this
|
||||
case, the command stays executable, even if the outer command does not run.
|
||||
For this to work, the object type needs to have a constructor that accepts
|
||||
no arguments.
|
||||
|
||||
As such it runs standalone:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@pass_repo
|
||||
def cp(repo):
|
||||
click.echo(repo)
|
||||
|
||||
As you can see:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cp, [])
|
219
docs/conf.py
Normal file
|
@ -0,0 +1,219 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# click documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Apr 26 19:53:01 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
sys.path.append(os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
|
||||
'clickdoctools']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'click'
|
||||
copyright = u'2014, Armin Ronacher'
|
||||
|
||||
# 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 = 'click'
|
||||
|
||||
# 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 = {
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = 'click'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
'sourcelink.html', 'searchbox.html']
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# 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 = [
|
||||
('index', 'click.tex', u'click documentation',
|
||||
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
|
||||
}
|
162
docs/documentation.rst
Normal file
|
@ -0,0 +1,162 @@
|
|||
Documenting Scripts
|
||||
===================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Click makes it very easy to document your command line tools. First of
|
||||
all, it automatically generates help pages for you. While these are
|
||||
currently not customizable in terms of their layout, all of the text
|
||||
can be changed.
|
||||
|
||||
Help Texts
|
||||
----------
|
||||
|
||||
Commands and options accept help arguments. In the case of commands, the
|
||||
docstring of the function is automatically used if provided.
|
||||
|
||||
Simple example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--count', default=1, help='number of greetings')
|
||||
@click.argument('name')
|
||||
def hello(count, name):
|
||||
"""This script prints hello NAME COUNT times."""
|
||||
for x in range(count):
|
||||
click.echo('Hello %s!' % name)
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, args=['--help'])
|
||||
|
||||
Arguments cannot be documented this way. This is to follow the general
|
||||
convention of Unix tools of using arguments for only the most necessary
|
||||
things and to document them in the introduction text by referring to them
|
||||
by name.
|
||||
|
||||
Preventing Rewrapping
|
||||
---------------------
|
||||
|
||||
The default behavior of Click is to rewrap text based on the width of the
|
||||
terminal. In some circumstances, this can become a problem. The main issue
|
||||
is when showing code examples, where newlines are significant.
|
||||
|
||||
Rewrapping can be disabled on a per-paragraph basis by adding a line with
|
||||
solely the ``\b`` escape marker in it. This line will be removed from the
|
||||
help text and rewrapping will be disabled.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""First paragraph.
|
||||
|
||||
This is a very long second paragraph and as you
|
||||
can see wrapped very early in the source text
|
||||
but will be rewrapped to the terminal width in
|
||||
the final output.
|
||||
|
||||
\b
|
||||
This is
|
||||
a paragraph
|
||||
without rewrapping.
|
||||
|
||||
And this is a paragraph
|
||||
that will be rewrapped again.
|
||||
"""
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, args=['--help'])
|
||||
|
||||
|
||||
Meta Variables
|
||||
--------------
|
||||
|
||||
Options and parameters accept a ``metavar`` argument that can change the
|
||||
meta variable in the help page. The default version is the parameter name
|
||||
in uppercase with underscores, but can be annotated differently if
|
||||
desired. This can be customized at all levels:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command(options_metavar='<options>')
|
||||
@click.option('--count', default=1, help='number of greetings',
|
||||
metavar='<int>')
|
||||
@click.argument('name', metavar='<name>')
|
||||
def hello(count, name):
|
||||
"""This script prints hello <name> <int> times."""
|
||||
for x in range(count):
|
||||
click.echo('Hello %s!' % name)
|
||||
|
||||
Example:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, args=['--help'])
|
||||
|
||||
|
||||
Command Short Help
|
||||
------------------
|
||||
|
||||
For commands, a short help snippet is generated. By default, it's the first
|
||||
sentence of the help message of the command, unless it's too long. This can
|
||||
also be overridden:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""A simple command line tool."""
|
||||
|
||||
@cli.command('init', short_help='init the repo')
|
||||
def init():
|
||||
"""Initializes the repository."""
|
||||
|
||||
@cli.command('delete', short_help='delete the repo')
|
||||
def delete():
|
||||
"""Deletes the repository."""
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='repo.py')
|
||||
|
||||
|
||||
Help Parameter Customization
|
||||
----------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
The help parameter is implemented in Click in a very special manner.
|
||||
Unlike regular parameters it's automatically added by Click for any
|
||||
command and it performs automatic conflict resolution. By default it's
|
||||
called ``--help``, but this can be changed. If a command itself implements
|
||||
a parameter with the same name, the default help parameter stops accepting
|
||||
it. There is a context setting that can be used to override the names of
|
||||
the help parameters called :attr:`~Context.help_option_names`.
|
||||
|
||||
This example changes the default parameters to ``-h`` and ``--help``
|
||||
instead of just ``--help``:
|
||||
|
||||
.. click:example::
|
||||
|
||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||
|
||||
@click.command(context_settings=CONTEXT_SETTINGS)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, ['-h'])
|
73
docs/exceptions.rst
Normal file
|
@ -0,0 +1,73 @@
|
|||
Exception Handling
|
||||
==================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Click internally uses exceptions to signal various error conditions that
|
||||
the user of the application might have caused. Primarily this is things
|
||||
like incorrect usage.
|
||||
|
||||
Where are Errors Handled?
|
||||
-------------------------
|
||||
|
||||
Click's main error handling is happening in :meth:`BaseCommand.main`. In
|
||||
there it handles all subclasses of :exc:`ClickException` as well as the
|
||||
standard :exc:`EOFError` and :exc:`KeyboardInterrupt` exceptions. The
|
||||
latter are internally translated into a :exc:`Abort`.
|
||||
|
||||
The logic applied is the following:
|
||||
|
||||
1. If an :exc:`EOFError` or :exc:`KeyboardInterrupt` happens, reraise it
|
||||
as :exc:`Abort`.
|
||||
2. If an :exc:`ClickException` is raised, invoke the
|
||||
:meth:`ClickException.show` method on it to display it and then exit
|
||||
the program with :attr:`ClickException.exit_code`.
|
||||
3. If an :exc:`Abort` exception is raised print the string ``Aborted!``
|
||||
to standard error and exit the program with exit code ``1``.
|
||||
4. if it goes through well, exit the program with exit code ``0``.
|
||||
|
||||
What if I don't want that?
|
||||
--------------------------
|
||||
|
||||
Generally you always have the option to invoke the :meth:`invoke` method
|
||||
yourself. For instance if you have a :class:`Command` you can invoke it
|
||||
manually like this::
|
||||
|
||||
ctx = command.make_context('command-name', ['args', 'go', 'here'])
|
||||
with ctx:
|
||||
result = command.invoke(ctx)
|
||||
|
||||
In this case exceptions will not be handled at all and bubbled up as you
|
||||
would expect.
|
||||
|
||||
Starting with Click 3.0 you can also use the :meth:`Command.main` method
|
||||
but disable the standalone mode which will do two things: disable
|
||||
exception handling and disable the implicit :meth:`sys.exit` at the end.
|
||||
|
||||
So you can do something like this::
|
||||
|
||||
command.main(['command-name', 'args', 'go', 'here'],
|
||||
standalone_mode=False)
|
||||
|
||||
Which Exceptions Exist?
|
||||
-----------------------
|
||||
|
||||
Click has two exception bases: :exc:`ClickException` which is raised for
|
||||
all exceptions that Click wants to signal to the user and :exc:`Abort`
|
||||
which is used to instruct Click to abort the execution.
|
||||
|
||||
A :exc:`ClickException` has a :meth:`~ClickException.show` method which
|
||||
can render an error message to stderr or the given file object. If you
|
||||
want to use the exception yourself for doing something check the API docs
|
||||
about what else they provide.
|
||||
|
||||
The following common subclasses exist:
|
||||
|
||||
* :exc:`UsageError` to inform the user that something went wrong.
|
||||
* :exc:`BadParameter` to inform the user that something went wrong with
|
||||
a specific parameter. These are often handled internally in Click and
|
||||
augmented with extra information if possible. For instance if those
|
||||
are raised from a callback Click will automatically augment it with
|
||||
the parameter name if possible.
|
||||
* :exc:`FileError` this is an error that is raised by the
|
||||
:exc:`FileType` if Click encounters issues opening the file.
|
98
docs/index.rst
Normal file
|
@ -0,0 +1,98 @@
|
|||
Welcome to the Click Documentation
|
||||
==================================
|
||||
|
||||
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
|
||||
|
||||
What does it look like? Here is an example of a simple Click program:
|
||||
|
||||
.. click:example::
|
||||
|
||||
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:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, ['--count=3'], prog_name='python hello.py', input='John\n')
|
||||
|
||||
It automatically generates nicely formatted help pages:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, ['--help'], prog_name='python hello.py')
|
||||
|
||||
You can get the library directly from PyPI::
|
||||
|
||||
pip install click
|
||||
|
||||
Documentation Contents
|
||||
----------------------
|
||||
|
||||
This part of the documentation guides you through all of the library's
|
||||
usage patterns.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
why
|
||||
quickstart
|
||||
setuptools
|
||||
parameters
|
||||
options
|
||||
arguments
|
||||
commands
|
||||
prompts
|
||||
documentation
|
||||
complex
|
||||
advanced
|
||||
testing
|
||||
utils
|
||||
bashcomplete
|
||||
exceptions
|
||||
python3
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
If you are looking for information on a specific function, class, or
|
||||
method, this part of the documentation is for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
|
||||
Miscellaneous Pages
|
||||
-------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
changelog
|
||||
upgrading
|
||||
license
|
13
docs/license.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
License
|
||||
=======
|
||||
|
||||
Click is licensed under a three-clause BSD License. It basically means:
|
||||
do whatever you want with it as long as the copyright in Click sticks
|
||||
around, the conditions are not modified and the disclaimer is present.
|
||||
Furthermore, you must not use the names of the authors to promote derivatives
|
||||
of the software without written consent.
|
||||
|
||||
License Text
|
||||
------------
|
||||
|
||||
.. include:: ../LICENSE
|
155
docs/make.bat
Normal file
|
@ -0,0 +1,155 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Classy.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Classy.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
611
docs/options.rst
Normal file
|
@ -0,0 +1,611 @@
|
|||
.. _options:
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Adding options to commands can be accomplished by the :func:`option`
|
||||
decorator. Since options can come in various different versions, there
|
||||
are a ton of parameters to configure their behavior. Options in click are
|
||||
distinct from :ref:`positional arguments <arguments>`.
|
||||
|
||||
Basic Value Options
|
||||
-------------------
|
||||
|
||||
The most basic option is a value option. These options accept one
|
||||
argument which is a value. If no type is provided, the type of the default
|
||||
value is used. If no default value is provided, the type is assumed to be
|
||||
:data:`STRING`. By default, the name of the parameter is the first long
|
||||
option defined; otherwise the first short one is used.
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--n', default=1)
|
||||
def dots(n):
|
||||
click.echo('.' * n)
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(dots, args=['--n=2'])
|
||||
|
||||
In this case the option is of type :data:`INT` because the default value
|
||||
is an integer.
|
||||
|
||||
Multi Value Options
|
||||
-------------------
|
||||
|
||||
Sometimes, you have options that take more than one argument. For options,
|
||||
only a fixed number of arguments is supported. This can be configured by
|
||||
the ``nargs`` parameter. The values are then stored as a tuple.
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--pos', nargs=2, type=float)
|
||||
def findme(pos):
|
||||
click.echo('%s / %s' % pos)
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(findme, args=['--pos', '2.0', '3.0'])
|
||||
|
||||
Multiple Options
|
||||
----------------
|
||||
|
||||
Similarly to ``nargs``, there is also the case of wanting to support a
|
||||
parameter being provided multiple times to and have all values recorded --
|
||||
not just the last one. For instance, ``git commit -m foo -m bar`` would
|
||||
record two lines for the commit message: ``foo`` and ``bar``. This can be
|
||||
accomplished with the ``multiple`` flag:
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--message', '-m', multiple=True)
|
||||
def commit(message):
|
||||
click.echo('\n'.join(message))
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(commit, args=['-m', 'foo', '-m', 'bar'])
|
||||
|
||||
Counting
|
||||
--------
|
||||
|
||||
In some very rare circumstances, it is interesting to use the repetition
|
||||
of options to count an integer up. This can be used for verbosity flags,
|
||||
for instance:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('-v', '--verbose', count=True)
|
||||
def log(verbose):
|
||||
click.echo('Verbosity: %s' % verbose)
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(log, args=['-vvv'])
|
||||
|
||||
Boolean Flags
|
||||
-------------
|
||||
|
||||
Boolean flags are options that can be enabled or disabled. This can be
|
||||
accomplished by defining two flags in one go separated by a slash (``/``)
|
||||
for enabling or disabling the option. (If a slash is in an option string,
|
||||
Click automatically knows that it's a boolean flag and will pass
|
||||
``is_flag=True`` implicitly.) Click always wants you to provide an enable
|
||||
and disable flag so that you can change the default later.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import os
|
||||
|
||||
@click.command()
|
||||
@click.option('--shout/--no-shout', default=False)
|
||||
def info(shout):
|
||||
rv = os.uname()[0]
|
||||
if shout:
|
||||
rv = rv.upper() + '!!!!111'
|
||||
click.echo(rv)
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(info, args=['--shout'])
|
||||
invoke(info, args=['--no-shout'])
|
||||
|
||||
If you really don't want an off-switch, you can just define one and
|
||||
manually inform Click that something is a flag:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import os
|
||||
|
||||
@click.command()
|
||||
@click.option('--shout', is_flag=True)
|
||||
def info(shout):
|
||||
rv = os.uname()[0]
|
||||
if shout:
|
||||
rv = rv.upper() + '!!!!111'
|
||||
click.echo(rv)
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(info, args=['--shout'])
|
||||
|
||||
Note that if a slash is contained in your option already (for instance, if
|
||||
you use Windows-style parameters where ``/`` is the prefix character), you
|
||||
can alternatively split the parameters through ``;`` instead:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('/debug;/no-debug')
|
||||
def log(debug):
|
||||
click.echo('debug=%s' % debug)
|
||||
|
||||
if __name__ == '__main__':
|
||||
log()
|
||||
|
||||
Feature Switches
|
||||
----------------
|
||||
|
||||
In addition to boolean flags, there are also feature switches. These are
|
||||
implemented by setting multiple options to the same parameter name and
|
||||
defining a flag value. Note that by providing the ``flag_value`` parameter,
|
||||
Click will implicitly set ``is_flag=True``.
|
||||
|
||||
To set a default flag, assign a value of `True` to the flag that should be
|
||||
the default.
|
||||
|
||||
.. click:example::
|
||||
|
||||
import os
|
||||
|
||||
@click.command()
|
||||
@click.option('--upper', 'transformation', flag_value='upper',
|
||||
default=True)
|
||||
@click.option('--lower', 'transformation', flag_value='lower')
|
||||
def info(transformation):
|
||||
click.echo(getattr(os.uname()[0], transformation)())
|
||||
|
||||
And on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(info, args=['--upper'])
|
||||
invoke(info, args=['--lower'])
|
||||
invoke(info)
|
||||
|
||||
.. _choice-opts:
|
||||
|
||||
Choice Options
|
||||
--------------
|
||||
|
||||
Sometimes, you want to have a parameter be a choice of a list of values.
|
||||
In that case you can use :class:`Choice` type. It can be instantiated
|
||||
with a list of valid values.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--hash-type', type=click.Choice(['md5', 'sha1']))
|
||||
def digest(hash_type):
|
||||
click.echo(hash_type)
|
||||
|
||||
What it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(digest, args=['--hash-type=md5'])
|
||||
println()
|
||||
invoke(digest, args=['--hash-type=foo'])
|
||||
println()
|
||||
invoke(digest, args=['--help'])
|
||||
|
||||
.. _option-prompting:
|
||||
|
||||
Prompting
|
||||
---------
|
||||
|
||||
In some cases, you want parameters that can be provided from the command line,
|
||||
but if not provided, ask for user input instead. This can be implemented with
|
||||
Click by defining a prompt string.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--name', prompt=True)
|
||||
def hello(name):
|
||||
click.echo('Hello %s!' % name)
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, args=['--name=John'])
|
||||
invoke(hello, input=['John'])
|
||||
|
||||
If you are not happy with the default prompt string, you can ask for
|
||||
a different one:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--name', prompt='Your name please')
|
||||
def hello(name):
|
||||
click.echo('Hello %s!' % name)
|
||||
|
||||
What it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, input=['John'])
|
||||
|
||||
Password Prompts
|
||||
----------------
|
||||
|
||||
Click also supports hidden prompts and asking for confirmation. This is
|
||||
useful for password input:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--password', prompt=True, hide_input=True,
|
||||
confirmation_prompt=True)
|
||||
def encrypt(password):
|
||||
click.echo('Encrypting password to %s' % password.encode('rot13'))
|
||||
|
||||
What it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(encrypt, input=['secret', 'secret'])
|
||||
|
||||
Because this combination of parameters is quite common, this can also be
|
||||
replaced with the :func:`password_option` decorator:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.password_option()
|
||||
def encrypt(password):
|
||||
click.echo('Encrypting password to %s' % password.encode('rot13'))
|
||||
|
||||
Callbacks and Eager Options
|
||||
---------------------------
|
||||
|
||||
Sometimes, you want a parameter to completely change the execution flow.
|
||||
For instance, this is the case when you want to have a ``--version``
|
||||
parameter that prints out the version and then exits the application.
|
||||
|
||||
In such cases, you need two concepts: eager parameters and a callback. An
|
||||
eager parameter is a parameter that is handled before others, and a
|
||||
callback is what executes after the parameter is handled. The eagerness
|
||||
is necessary so that an earlier required parameter does not produce an
|
||||
error message. For instance, if ``--version`` was not eager and a
|
||||
parameter ``--foo`` was required and defined before, you would need to
|
||||
specify it for ``--version`` to work. For more information, see
|
||||
:ref:`callback-evaluation-order`.
|
||||
|
||||
A callback is a function that is invoked with two parameters: the current
|
||||
:class:`Context` and the value. The context provides some useful features
|
||||
such as quitting the application and gives access to other already
|
||||
processed parameters.
|
||||
|
||||
Here an example for a ``--version`` flag:
|
||||
|
||||
.. click:example::
|
||||
|
||||
def print_version(ctx, param, value):
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
click.echo('Version 1.0')
|
||||
ctx.exit()
|
||||
|
||||
@click.command()
|
||||
@click.option('--version', is_flag=True, callback=print_version,
|
||||
expose_value=False, is_eager=True)
|
||||
def hello():
|
||||
click.echo('Hello World!')
|
||||
|
||||
The `expose_value` parameter prevents the pretty pointless ``version``
|
||||
parameter from being passed to the callback. If that was not specified, a
|
||||
boolean would be passed to the `hello` script. The `resilient_parsing`
|
||||
flag is applied to the context if Click wants to parse the command line
|
||||
without any destructive behavior that would change the execution flow. In
|
||||
this case, because we would exit the program, we instead do nothing.
|
||||
|
||||
What it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello)
|
||||
invoke(hello, args=['--version'])
|
||||
|
||||
.. admonition:: Callback Signature Changes
|
||||
|
||||
In Click 2.0 the signature for callbacks changed. For more
|
||||
information about these changes see :ref:`upgrade-to-2.0`.
|
||||
|
||||
Yes Parameters
|
||||
--------------
|
||||
|
||||
For dangerous operations, it's very useful to be able to ask a user for
|
||||
confirmation. This can be done by adding a boolean ``--yes`` flag and
|
||||
asking for confirmation if the user did not provide it and to fail in a
|
||||
callback:
|
||||
|
||||
.. click:example::
|
||||
|
||||
def abort_if_false(ctx, param, value):
|
||||
if not value:
|
||||
ctx.abort()
|
||||
|
||||
@click.command()
|
||||
@click.option('--yes', is_flag=True, callback=abort_if_false,
|
||||
expose_value=False,
|
||||
prompt='Are you sure you want to drop the db?')
|
||||
def dropdb():
|
||||
click.echo('Dropped all tables!')
|
||||
|
||||
And what it looks like on the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(dropdb, input=['n'])
|
||||
invoke(dropdb, args=['--yes'])
|
||||
|
||||
Because this combination of parameters is quite common, this can also be
|
||||
replaced with the :func:`confirmation_option` decorator:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.confirmation_option(help='Are you sure you want to drop the db?')
|
||||
def dropdb():
|
||||
click.echo('Dropped all tables!')
|
||||
|
||||
.. admonition:: Callback Signature Changes
|
||||
|
||||
In Click 2.0 the signature for callbacks changed. For more
|
||||
information about these changes see :ref:`upgrade-to-2.0`.
|
||||
|
||||
Values from Environment Variables
|
||||
---------------------------------
|
||||
|
||||
A very useful feature of Click is the ability to accept parameters from
|
||||
environment variables in addition to regular parameters. This allows
|
||||
tools to be automated much easier. For instance, you might want to pass
|
||||
a configuration file with a ``--config`` parameter but also support exporting
|
||||
a ``TOOL_CONFIG=hello.cfg`` key-value pair for a nicer development
|
||||
experience.
|
||||
|
||||
This is supported by Click in two ways. One is to automatically build
|
||||
environment variables which is supported for options only. To enable this
|
||||
feature, the ``auto_envvar_prefix`` parameter needs to be passed to the
|
||||
script that is invoked. Each command and parameter is then added as an
|
||||
uppercase underscore-separated variable. If you have a subcommand
|
||||
called ``foo`` taking an option called ``bar`` and the prefix is
|
||||
``MY_TOOL``, then the variable is ``MY_TOOL_FOO_BAR``.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--username')
|
||||
def greet(username):
|
||||
click.echo('Hello %s!' % username)
|
||||
|
||||
if __name__ == '__main__':
|
||||
greet(auto_envvar_prefix='GREETER')
|
||||
|
||||
And from the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(greet, env={'GREETER_USERNAME': 'john'},
|
||||
auto_envvar_prefix='GREETER')
|
||||
|
||||
The second option is to manually pull values in from specific environment
|
||||
variables by defining the name of the environment variable on the option.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--username', envvar='USERNAME')
|
||||
def greet(username):
|
||||
click.echo('Hello %s!' % username)
|
||||
|
||||
if __name__ == '__main__':
|
||||
greet()
|
||||
|
||||
And from the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(greet, env={'USERNAME': 'john'})
|
||||
|
||||
In that case it can also be a list of different environment variables
|
||||
where the first one is picked.
|
||||
|
||||
Multiple Values from Environment Values
|
||||
---------------------------------------
|
||||
|
||||
As options can accept multiple values, pulling in such values from
|
||||
environment variables (which are strings) is a bit more complex. The way
|
||||
Click solves this is by leaving it up to the type to customize this
|
||||
behavior. For both ``multiple`` and ``nargs`` with values other than
|
||||
``1``, Click will invoke the :meth:`ParamType.split_envvar_value` method to
|
||||
perform the splitting.
|
||||
|
||||
The default implementation for all types is to split on whitespace. The
|
||||
exceptions to this rule are the :class:`File` and :class:`Path` types
|
||||
which both split according to the operating system's path splitting rules.
|
||||
On Unix systems like Linux and OS X, the splitting happens for those on
|
||||
every colon (``:``), and for Windows, on every semicolon (``;``).
|
||||
|
||||
Example usage:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('paths', '--path', envvar='PATHS', multiple=True,
|
||||
type=click.Path())
|
||||
def perform(paths):
|
||||
for path in paths:
|
||||
click.echo(path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
perform()
|
||||
|
||||
And from the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
import os
|
||||
invoke(perform, env={'PATHS': './foo/bar%s./test' % os.path.pathsep})
|
||||
|
||||
Other Prefix Characters
|
||||
-----------------------
|
||||
|
||||
Click can deal with alternative prefix characters other than ``-`` for
|
||||
options. This is for instance useful if you want to handle slashes as
|
||||
parameters ``/`` or something similar. Note that this is strongly
|
||||
discouraged in general because Click wants developers to stay close to
|
||||
POSIX semantics. However in certain situations this can be useful:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('+w/-w')
|
||||
def chmod(w):
|
||||
click.echo('writable=%s' % w)
|
||||
|
||||
if __name__ == '__main__':
|
||||
chmod()
|
||||
|
||||
And from the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(chmod, args=['+w'])
|
||||
invoke(chmod, args=['-w'])
|
||||
|
||||
Note that if you are using ``/`` as prefix character and you want to use a
|
||||
boolean flag you need to separate it with ``;`` instead of ``/``:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('/debug;/no-debug')
|
||||
def log(debug):
|
||||
click.echo('debug=%s' % debug)
|
||||
|
||||
if __name__ == '__main__':
|
||||
log()
|
||||
|
||||
.. _ranges:
|
||||
|
||||
Range Options
|
||||
-------------
|
||||
|
||||
A special mention should go to the :class:`IntRange` type, which works very
|
||||
similarly to the :data:`INT` type, but restricts the value to fall into a
|
||||
specific range (inclusive on both edges). It has two modes:
|
||||
|
||||
- the default mode (non-clamping mode) where a value that falls outside
|
||||
of the range will cause an error.
|
||||
- an optional clamping mode where a value that falls outside of the
|
||||
range will be clamped. This means that a range of ``0-5`` would
|
||||
return ``5`` for the value ``10`` or ``0`` for the value ``-1`` (for
|
||||
example).
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--count', type=click.IntRange(0, 20, clamp=True))
|
||||
@click.option('--digit', type=click.IntRange(0, 10))
|
||||
def repeat(count, digit):
|
||||
click.echo(str(digit) * count)
|
||||
|
||||
if __name__ == '__main__':
|
||||
repeat()
|
||||
|
||||
And from the command line:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(repeat, args=['--count=1000', '--digit=5'])
|
||||
invoke(repeat, args=['--count=1000', '--digit=12'])
|
||||
|
||||
If you pass ``None`` for any of the edges, it means that the range is open
|
||||
at that side.
|
||||
|
||||
Callbacks for Validation
|
||||
------------------------
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
|
||||
If you want to apply custom validation logic, you can do this in the
|
||||
parameter callbacks. These callbacks can both modify values as well as
|
||||
raise errors if the validation does not work.
|
||||
|
||||
In Click 1.0, you can only raise the :exc:`UsageError` but starting with
|
||||
Click 2.0, you can also raise the :exc:`BadParameter` error, which has the
|
||||
added advantage that it will automatically format the error message to
|
||||
also contain the parameter name.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
def validate_rolls(ctx, param, value):
|
||||
try:
|
||||
rolls, dice = map(int, value.split('d', 2))
|
||||
return (dice, rolls)
|
||||
except ValueError:
|
||||
raise click.BadParameter('rolls need to be in format NdM')
|
||||
|
||||
@click.command()
|
||||
@click.option('--rolls', callback=validate_rolls, default='1d6')
|
||||
def roll(rolls):
|
||||
click.echo('Rolling a %d-sided dice %d time(s)' % rolls)
|
||||
|
||||
if __name__ == '__main__':
|
||||
roll()
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(roll, args=['--rolls=42'])
|
||||
println()
|
||||
invoke(roll, args=['--rolls=2d12'])
|
120
docs/parameters.rst
Normal file
|
@ -0,0 +1,120 @@
|
|||
Parameters
|
||||
==========
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Click supports two types of parameters for scripts: options and arguments.
|
||||
There is generally some confusion among authors of command line scripts of
|
||||
when to use which, so here is a quick overview of the differences. As its
|
||||
name indicates, an option is optional. While arguments can be optional
|
||||
within reason, they are much more restricted in how optional they can be.
|
||||
|
||||
To help you decide between options and arguments, the recommendation is
|
||||
to use arguments exclusively for things like going to subcommands or input
|
||||
filenames / URLs, and have everything else be an option instead.
|
||||
|
||||
Differences
|
||||
-----------
|
||||
|
||||
Arguments can do less than options. The following features are only
|
||||
available for options:
|
||||
|
||||
* automatic prompting for missing input
|
||||
* act as flags (boolean or otherwise)
|
||||
* option values can be pulled from environment variables, arguments can not
|
||||
* options are fully documented in the help page, arguments are not
|
||||
(this is intentional as arguments might be too specific to be
|
||||
automatically documented)
|
||||
|
||||
On the other hand arguments unlike options can accept an arbitrary number
|
||||
of arguments. Options can strictly ever only accept a fixed number of
|
||||
arguments (defaults to 1).
|
||||
|
||||
Parameter Types
|
||||
---------------
|
||||
|
||||
Parameters can be of different types. Types can be implemented with
|
||||
different behavior and some are supported out of the box:
|
||||
|
||||
``str`` / :data:`click.STRING`:
|
||||
The default parameter type which indicates unicode strings.
|
||||
|
||||
``int`` / :data:`click.INT`:
|
||||
A parameter that only accepts integers.
|
||||
|
||||
``float`` / :data:`click.FLOAT`:
|
||||
A parameter that only accepts floating point values.
|
||||
|
||||
``bool`` / :data:`click.BOOL`:
|
||||
A parameter that accepts boolean values. This is automatically used
|
||||
for boolean flags. If used with string values ``1``, ``yes``, ``y``
|
||||
and ``true`` convert to `True` and ``0``, ``no``, ``n`` and ``false``
|
||||
convert to `False`.
|
||||
|
||||
:data:`click.UUID`:
|
||||
A parameter that accepts UUID values. This is not automatically
|
||||
guessed but represented as :class:`uuid.UUID`.
|
||||
|
||||
.. autoclass:: File
|
||||
:noindex:
|
||||
|
||||
.. autoclass:: Path
|
||||
:noindex:
|
||||
|
||||
.. autoclass:: Choice
|
||||
:noindex:
|
||||
|
||||
.. autoclass:: IntRange
|
||||
:noindex:
|
||||
|
||||
Custom parameter types can be implemented by subclassing
|
||||
:class:`click.ParamType`. For simple cases, passing a Python function that
|
||||
fails with a `ValueError` is also supported, though discouraged.
|
||||
|
||||
Parameter Names
|
||||
---------------
|
||||
|
||||
Parameters (both options and arguments) accept a number of positional
|
||||
arguments which are the parameter declarations. Each string with a
|
||||
single dash is added as short argument; each string starting with a double
|
||||
dash as long one. If a string is added without any dashes, it becomes the
|
||||
internal parameter name which is also used as variable name.
|
||||
|
||||
If a parameter is not given a name without dashes, a name is generated
|
||||
automatically by taking the longest argument and converting all dashes to
|
||||
underscores. For an option with ``('-f', '--foo-bar')``, the parameter
|
||||
name is `foo_bar`. For an option with ``('-x',)``, the parameter is `x`.
|
||||
For an option with ``('-f', '--filename', 'dest')``, the parameter is
|
||||
called `dest`.
|
||||
|
||||
Implementing Custom Types
|
||||
-------------------------
|
||||
|
||||
To implement a custom type, you need to subclass the :class:`ParamType`
|
||||
class. Types can be invoked with or without context and parameter object,
|
||||
which is why they need to be able to deal with this.
|
||||
|
||||
The following code implements an integer type that accepts hex and octal
|
||||
numbers in addition to normal integers, and converts them into regular
|
||||
integers::
|
||||
|
||||
import click
|
||||
|
||||
class BasedIntParamType(click.ParamType):
|
||||
name = 'integer'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
if value[:2].lower() == '0x':
|
||||
return int(value[2:], 16)
|
||||
elif value[:1] == '0':
|
||||
return int(value, 8)
|
||||
return int(value, 10)
|
||||
except ValueError:
|
||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||
|
||||
BASED_INT = BasedIntParamType()
|
||||
|
||||
As you can see, a subclass needs to implement the :meth:`ParamType.convert`
|
||||
method and optionally provide the :attr:`ParamType.name` attribute. The
|
||||
latter can be used for documentation purposes.
|
48
docs/prompts.rst
Normal file
|
@ -0,0 +1,48 @@
|
|||
User Input Prompts
|
||||
==================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Click supports prompts in two different places. The first is automated
|
||||
prompts when the parameter handling happens, and the second is to ask for
|
||||
prompts at a later point independently.
|
||||
|
||||
This can be accomplished with the :func:`prompt` function, which asks for
|
||||
valid input according to a type, or the :func:`confirm` function, which asks
|
||||
for confirmation (yes/no).
|
||||
|
||||
Option Prompts
|
||||
--------------
|
||||
|
||||
Option prompts are integrated into the option interface. See
|
||||
:ref:`option-prompting` for more information. Internally, it
|
||||
automatically calls either :func:`prompt` or :func:`confirm` as necessary.
|
||||
|
||||
Input Prompts
|
||||
-------------
|
||||
|
||||
To manually ask for user input, you can use the :func:`prompt` function.
|
||||
By default, it accepts any Unicode string, but you can ask for any other
|
||||
type. For instance, you can ask for a valid integer::
|
||||
|
||||
value = click.prompt('Please enter a valid integer', type=int)
|
||||
|
||||
Additionally, the type will be determined automatically if a default value is
|
||||
provided. For instance, the following will only accept floats::
|
||||
|
||||
value = click.prompt('Please enter a number', default=42.0)
|
||||
|
||||
Confirmation Prompts
|
||||
--------------------
|
||||
|
||||
To ask if a user wants to continue with an action, the :func:`confirm`
|
||||
function comes in handy. By default, it returns the result of the prompt
|
||||
as a boolean value::
|
||||
|
||||
if click.confirm('Do you want to continue?'):
|
||||
click.echo('Well done!')
|
||||
|
||||
There is also the option to make the function automatically abort the
|
||||
execution of the program if it does not return ``True``::
|
||||
|
||||
click.confirm('Do you want to continue?', abort=True)
|
154
docs/python3.rst
Normal file
|
@ -0,0 +1,154 @@
|
|||
Python 3 Support
|
||||
================
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Click supports Python 3, but like all other command line utility libraries,
|
||||
it suffers from the Unicode text model in Python 3. All examples in the
|
||||
documentation were written so that they could run on both Python 2.x and
|
||||
Python 3.3 or higher.
|
||||
|
||||
At the moment, it is strongly recommended is to use Python 2 for Click
|
||||
utilities unless Python 3 is a hard requirement.
|
||||
|
||||
.. _python3-limitations:
|
||||
|
||||
Python 3 Limitations
|
||||
--------------------
|
||||
|
||||
At the moment, Click suffers from a few problems with Python 3:
|
||||
|
||||
* The command line in Unix traditionally is in bytes, and not Unicode.
|
||||
While there are encoding hints for all of this, there are generally
|
||||
some situations where this can break. The most common one is SSH
|
||||
connections to machines with different locales.
|
||||
|
||||
Misconfigured environments can currently cause a wide range of Unicode
|
||||
problems in Python 3 due to the lack of support for roundtripping
|
||||
surrogate escapes. This will not be fixed in Click itself!
|
||||
|
||||
For more information see :ref:`python3-surrogates`.
|
||||
|
||||
* Standard input and output in Python 3 is opened in Unicode mode by
|
||||
default. Click has to reopen the stream in binary mode in certain
|
||||
situations. Because there is no standardized way to do this, this
|
||||
might not always work. Primarily this can become a problem when
|
||||
testing command-line applications.
|
||||
|
||||
This is not supported::
|
||||
|
||||
sys.stdin = io.StringIO('Input here')
|
||||
sys.stdout = io.StringIO()
|
||||
|
||||
Instead you need to do this::
|
||||
|
||||
input = 'Input here'
|
||||
in_stream = io.BytesIO(input.encode('utf-8'))
|
||||
sys.stdin = io.TextIOWrapper(in_stream, encoding='utf-8')
|
||||
out_stream = io.BytesIO()
|
||||
sys.stdout = io.TextIOWrapper(out_stream, encoding='utf-8')
|
||||
|
||||
Remember that in that case, you need to use ``out_stream.getvalue()``
|
||||
and not ``sys.stdout.getvalue()`` if you want to access the buffer
|
||||
contents as the wrapper will not forward that method.
|
||||
|
||||
Python 2 and 3 Differences
|
||||
--------------------------
|
||||
|
||||
Click attempts to minimize the differences between Python 2 and Python 3
|
||||
by following the best practices for both languages.
|
||||
|
||||
In Python 2, the following is true:
|
||||
|
||||
* ``sys.stdin``, ``sys.stdout``, and ``sys.stderr`` are opened in binary
|
||||
mode, but under some circumstances they support Unicode output. Click
|
||||
attempts to not subvert this but provides support for forcing streams
|
||||
to be Unicode-based.
|
||||
* ``sys.argv`` is always byte-based. Click will pass bytes to all
|
||||
input types and convert as necessary. The :class:`STRING` type
|
||||
automatically will decode properly the input value into a string by
|
||||
trying the most appropriate encodings.
|
||||
* When dealing with files, Click will never go through the Unicode APIs
|
||||
and will instead use the operating system's byte APIs to open the
|
||||
files.
|
||||
|
||||
In Python 3, the following is true:
|
||||
|
||||
* ``sys.stdin``, ``sys.stdout`` and ``sys.stderr`` are by default
|
||||
text-based. When Click needs a binary stream, it attempts to discover
|
||||
the underlying binary stream. See :ref:`python3-limitations` for how
|
||||
this works.
|
||||
* ``sys.argv`` is always Unicode-based. This also means that the native
|
||||
type for input values to the types in Click is Unicode, and not bytes.
|
||||
|
||||
This causes problems when the terminal is incorrectly set and Python
|
||||
does not figure out the encoding. In that case, the Unicode string
|
||||
will contain error bytes encoded as surrogate escapes.
|
||||
* When dealing with files, Click will always use the Unicode file system
|
||||
API calls by using the operating system's reported or guessed
|
||||
filesystem encoding. Surrogates are supported for filenames, so it
|
||||
should be possible to open files through the :class:`File` type even
|
||||
if the environment is misconfigured.
|
||||
|
||||
.. _python3-surrogates:
|
||||
|
||||
Python 3 Surrogate Handling
|
||||
---------------------------
|
||||
|
||||
Click in Python 3 does all the Unicode handling in the standard library
|
||||
and is subject to its behavior. In Python 2, Click does all the Unicode
|
||||
handling itself, which means there are differences in error behavior.
|
||||
|
||||
The most glaring difference is that in Python 2, Unicode will "just work",
|
||||
while in Python 3, it requires extra care. The reason for this is that on
|
||||
Python 3, the encoding detection is done in the interpreter and on Linux
|
||||
and certain other operating systems its encoding handling is problematic.
|
||||
|
||||
The biggest source of frustration is that Click scripts invoked by
|
||||
init systems (sysvinit, upstart, systemd, etc.), deployment tools (salt,
|
||||
puppet), or cron jobs (cron) will refuse to work unless a Unicode locale is
|
||||
exported.
|
||||
|
||||
If Click encounters such an environment it will prevent further execution
|
||||
to force you to set a locale. This is done because Click cannot know
|
||||
about the state of the system once it's invoked and restore the values
|
||||
before Python's Unicode handling kicked in.
|
||||
|
||||
If you see something like this error in Python 3::
|
||||
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
RuntimeError: Click will abort further execution because Python 3 was
|
||||
configured to use ASCII as encoding for the environment. Either switch
|
||||
to Python 2 or consult for http://click.pocoo.org/python3/
|
||||
mitigation steps.
|
||||
|
||||
You are dealing with an environment where Python 3 thinks you are
|
||||
restricted to ASCII data. The solution to these problems is different
|
||||
depending on which locale your computer is running in.
|
||||
|
||||
For instance, if you have a German Linux machine, you can fix the problem
|
||||
by exporting the locale to ``de_DE.utf-8``::
|
||||
|
||||
export LC_ALL=de_DE.utf-8
|
||||
export LANG=de_DE.utf-8
|
||||
|
||||
If you are on a US machine, ``en_EN.utf-8`` is the encoding of choice. On
|
||||
some newer Linux systems, you could also try ``C.UTF-8`` as the locale::
|
||||
|
||||
export LC_ALL=C.UTF-8
|
||||
export LANG=C.UTF-8
|
||||
|
||||
You need to do this before you invoke your Python script. If you are
|
||||
curious about the reasons for this, you can join the discussions in the
|
||||
Python 3 bug tracker:
|
||||
|
||||
* `ASCII is a bad filesystem default encoding
|
||||
<http://bugs.python.org/issue13643#msg149941>`_
|
||||
* `Use surrogateescape as default error handler
|
||||
<http://bugs.python.org/issue19977>`_
|
||||
* `Python 3 raises Unicode errors in the C locale
|
||||
<http://bugs.python.org/issue19846>`_
|
||||
* `LC_CTYPE=C: pydoc leaves terminal in an unusable state
|
||||
<http://bugs.python.org/issue21398>`_ (this is relevant to Click
|
||||
because the pager support is provided by the stdlib pydoc module)
|
274
docs/quickstart.rst
Normal file
|
@ -0,0 +1,274 @@
|
|||
Quickstart
|
||||
==========
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
You can get the library directly from PyPI::
|
||||
|
||||
pip install click
|
||||
|
||||
The installation into a :ref:`virtualenv` is heavily recommended.
|
||||
|
||||
.. _virtualenv:
|
||||
|
||||
virtualenv
|
||||
----------
|
||||
|
||||
Virtualenv is probably what you want to use for developing Click
|
||||
applications.
|
||||
|
||||
What problem does virtualenv solve? Chances are that you want to use it
|
||||
for other projects besides your Click script. But the more projects you
|
||||
have, the more likely it is that you will be working with different
|
||||
versions of Python itself, or at least different versions of Python
|
||||
libraries. Let's face it: quite often libraries break backwards
|
||||
compatibility, and it's unlikely that any serious application will have
|
||||
zero dependencies. So what do you do if two or more of your projects have
|
||||
conflicting dependencies?
|
||||
|
||||
Virtualenv to the rescue! Virtualenv enables multiple side-by-side
|
||||
installations of Python, one for each project. It doesn't actually
|
||||
install separate copies of Python, but it does provide a clever way to
|
||||
keep different project environments isolated. Let's see how virtualenv
|
||||
works.
|
||||
|
||||
If you are on Mac OS X or Linux, chances are that one of the following two
|
||||
commands will work for you::
|
||||
|
||||
$ sudo easy_install virtualenv
|
||||
|
||||
or even better::
|
||||
|
||||
$ sudo pip install virtualenv
|
||||
|
||||
One of these will probably install virtualenv on your system. Maybe it's even
|
||||
in your package manager. If you use Ubuntu, try::
|
||||
|
||||
$ sudo apt-get install python-virtualenv
|
||||
|
||||
If you are on Windows (or none of the above methods worked) you must install
|
||||
``pip`` first. For more information about this, see `installing pip`_.
|
||||
Once you have it installed, run the ``pip`` command from above, but without
|
||||
the `sudo` prefix.
|
||||
|
||||
.. _installing pip: http://pip.readthedocs.org/en/latest/installing.html
|
||||
|
||||
Once you have virtualenv installed, just fire up a shell and create
|
||||
your own environment. I usually create a project folder and a `venv`
|
||||
folder within::
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
$ virtualenv venv
|
||||
New python executable in venv/bin/python
|
||||
Installing setuptools, pip............done.
|
||||
|
||||
Now, whenever you want to work on a project, you only have to activate the
|
||||
corresponding environment. On OS X and Linux, do the following::
|
||||
|
||||
$ . venv/bin/activate
|
||||
|
||||
If you are a Windows user, the following command is for you::
|
||||
|
||||
$ venv\scripts\activate
|
||||
|
||||
Either way, you should now be using your virtualenv (notice how the prompt of
|
||||
your shell has changed to show the active environment).
|
||||
|
||||
And if you want to go back to the real world, use the following command::
|
||||
|
||||
$ deactivate
|
||||
|
||||
After doing this, the prompt of your shell should be as familar as before.
|
||||
|
||||
Now, let's move on. Enter the following command to get Flask activated in your
|
||||
virtualenv::
|
||||
|
||||
$ pip install Click
|
||||
|
||||
A few seconds later and you are good to go.
|
||||
|
||||
Screencast and Examples
|
||||
-----------------------
|
||||
|
||||
There is a screencast available which shows the basic API of Click and
|
||||
how to build simple applications with it. It also explores how to build
|
||||
commands with subcommands.
|
||||
|
||||
* `Building Command Line Applications with Click
|
||||
<https://www.youtube.com/watch?v=kNke39OZ2k0>`_
|
||||
|
||||
Examples of Click applications can be found in the documentation as well
|
||||
as in the GitHub repository together with readme files:
|
||||
|
||||
* ``inout``: `File input and output
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/inout>`_
|
||||
* ``naval``: `Port of docopt naval example
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/naval>`_
|
||||
* ``aliases``: `Command alias example
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/aliases>`_
|
||||
* ``repo``: `Git-/Mercurial-like command line interface
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/repo>`_
|
||||
* ``complex``: `Complex example with plugin loading
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/complex>`_
|
||||
* ``validation``: `Custom parameter validation example
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/validation>`_
|
||||
* ``colors``: `Colorama ANSI color support
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/colors>`_
|
||||
* ``termui``: `Terminal UI functions demo
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/termui>`_
|
||||
* ``imagepipe``: `Multi command chaining demo
|
||||
<https://github.com/mitsuhiko/click/tree/master/examples/imagepipe>`_
|
||||
|
||||
Basic Concepts
|
||||
--------------
|
||||
|
||||
Click is based on declaring commands through decorators. Internally, there
|
||||
is a non-decorator interface for advanced use cases, but it's discouraged
|
||||
for high-level usage.
|
||||
|
||||
A function becomes a Click command line tool by decorating it through
|
||||
:func:`click.command`. At its simplest, just decorating a function
|
||||
with this decorator will make it into a callable script:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def hello():
|
||||
click.echo('Hello World!')
|
||||
|
||||
What's happening is that the decorator converts the function into a
|
||||
:class:`Command` which then can be invoked::
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, args=[], prog_name='python hello.py')
|
||||
|
||||
And the corresponding help page:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, args=['--help'], prog_name='python hello.py')
|
||||
|
||||
Echoing
|
||||
-------
|
||||
|
||||
Why does this example use :func:`echo` instead of the regular
|
||||
:func:`print` function? The answer to this question is that Click
|
||||
attempts to support both Python 2 and Python 3 the same way and to be very
|
||||
robust even when the environment is misconfigured. Click wants to be
|
||||
functional at least on a basic level even if everything is completely
|
||||
broken.
|
||||
|
||||
What this means is that the :func:`echo` function applies some error
|
||||
correction in case the terminal is misconfigured instead of dying with an
|
||||
:exc:`UnicodeError`.
|
||||
|
||||
As an added benefit, starting with Click 2.0, the echo function also
|
||||
has good support for ANSI colors. It will automatically strip ANSI codes
|
||||
if the output stream is a file and if colorama is supported, ANSI colors
|
||||
will also work on Windows. See :ref:`ansi-colors` for more information.
|
||||
|
||||
If you don't need this, you can also use the `print()` construct /
|
||||
function.
|
||||
|
||||
Nesting Commands
|
||||
----------------
|
||||
|
||||
Commands can be attached to other commands of type :class:`Group`. This
|
||||
allows arbitrary nesting of scripts. As an example here is a script that
|
||||
implements two commands for managing databases:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@click.command()
|
||||
def initdb():
|
||||
click.echo('Initialized the database')
|
||||
|
||||
@click.command()
|
||||
def dropdb():
|
||||
click.echo('Dropped the database')
|
||||
|
||||
cli.add_command(initdb)
|
||||
cli.add_command(dropdb)
|
||||
|
||||
As you can see, the :func:`group` decorator works like the :func:`command`
|
||||
decorator, but creates a :class:`Group` object instead which can be given
|
||||
multiple subcommands that can be attached with :meth:`Group.add_command`.
|
||||
|
||||
For simple scripts, it's also possible to automatically attach and create a
|
||||
command by using the :meth:`Group.command` decorator instead. The above
|
||||
script can instead be written like this:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
def initdb():
|
||||
click.echo('Initialized the database')
|
||||
|
||||
@cli.command()
|
||||
def dropdb():
|
||||
click.echo('Dropped the database')
|
||||
|
||||
Adding Parameters
|
||||
-----------------
|
||||
|
||||
To add parameters, use the :func:`option` and :func:`argument` decorators:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--count', default=1, help='number of greetings')
|
||||
@click.argument('name')
|
||||
def hello(count, name):
|
||||
for x in range(count):
|
||||
click.echo('Hello %s!' % name)
|
||||
|
||||
What it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(hello, args=['--help'], prog_name='python hello.py')
|
||||
|
||||
.. _switching-to-setuptools:
|
||||
|
||||
Switching to Setuptools
|
||||
-----------------------
|
||||
|
||||
In the code you wrote so far there is a block at the end of the file which
|
||||
looks like this: ``if __name__ == '__main__':``. This is traditionally
|
||||
how a standalone Python file looks like. With Click you can continue
|
||||
doing that, but there are better ways through setuptools.
|
||||
|
||||
There are two main (and many more) reasons for this:
|
||||
|
||||
The first one is that setuptools automatically generates executable
|
||||
wrappers for Windows so your command line utilities work on Windows too.
|
||||
|
||||
The second reason is that setuptools scripts work with virtualenv on Unix
|
||||
without the virtualenv having to be activated. This is a very useful
|
||||
concept which allows you to bundle your scripts with all requirements into
|
||||
a virtualenv.
|
||||
|
||||
Click is perfectly equipped to work with that and in fact the rest of the
|
||||
documentation will assume that you are writing applications through
|
||||
setuptools.
|
||||
|
||||
I strongly recommend to have a look at the :ref:`setuptools-integration`
|
||||
chapter before reading the rest as the examples assume that you will
|
||||
be using setuptools.
|
137
docs/setuptools.rst
Normal file
|
@ -0,0 +1,137 @@
|
|||
.. _setuptools-integration:
|
||||
|
||||
Setuptools Integration
|
||||
======================
|
||||
|
||||
When writing command line utilities, it's recommended to write them as
|
||||
modules that are distributed with setuptools instead of using Unix
|
||||
shebangs.
|
||||
|
||||
Why would you want to do that? There are a bunch of reasons:
|
||||
|
||||
1. One of the problems with the traditional approach is that the first
|
||||
module the Python interpreter loads has an incorrect name. This might
|
||||
sound like a small issue but it has quite significant implications.
|
||||
|
||||
The first module is not called by it's actual name, but the
|
||||
interpreter renames it to ``__main__``. While that is a perfectly
|
||||
valid name it means that if another piece of code wants to import from
|
||||
that module it will trigger the import a second time under it's real
|
||||
name and all the sudden your code is imported twice.
|
||||
|
||||
2. Not on all platforms are things that easy to execute. On Linux and OS
|
||||
X you can add a comment to the beginning of the file (``#/usr/bin/env
|
||||
python``) and your script works like an executable (assuming it has
|
||||
the executable bit set). This however does not work on Windows.
|
||||
While on Windows you can associate interpreters with file extensions
|
||||
(like having everything ending in ``.py`` execute through the Python
|
||||
interpreter) you will then run into issues if you want to use the
|
||||
script in a virtualenv.
|
||||
|
||||
In fact running a script in a virtualenv is an issue with OS X and
|
||||
Linux as well. With the traditional approach you need to have the
|
||||
whole virtualenv activated so that the correct Python interpreter is
|
||||
used. Not very user friendly.
|
||||
|
||||
3. The main trick only works if the script is a Python module. If your
|
||||
application grows too large and you want to start using a package you
|
||||
will run into issues.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
To bundle your script with setuptools, all you need is the script in a
|
||||
Python package and a ``setup.py`` file.
|
||||
|
||||
Imagine this directory structure::
|
||||
|
||||
yourscript.py
|
||||
setup.py
|
||||
|
||||
Contents of ``yourscript.py``:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""Example script."""
|
||||
click.echo('Hello World!')
|
||||
|
||||
Contents of ``setup.py``::
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='yourscript',
|
||||
version='0.1',
|
||||
py_modules=['yourscript'],
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
yourscript=yourscript:cli
|
||||
''',
|
||||
)
|
||||
|
||||
The magic is in the ``entry_points`` parameter. Below
|
||||
``console_scripts``, each line identifies one console script. The first
|
||||
part before the equals sign (``=``) is the name of the script that should
|
||||
be generated, the second part is the import path followed by a colon
|
||||
(``:``) with the Click command.
|
||||
|
||||
That's it.
|
||||
|
||||
Testing The Script
|
||||
------------------
|
||||
|
||||
To test the script, you can make a new virtualenv and then install your
|
||||
package::
|
||||
|
||||
$ virtualenv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install --editable .
|
||||
|
||||
Afterwards, your command should be available:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(cli, prog_name='yourscript')
|
||||
|
||||
Scripts in Packages
|
||||
-------------------
|
||||
|
||||
If your script is growing and you want to switch over to your script being
|
||||
contained in a Python package the changes necessary are minimal. Let's
|
||||
assume your directory structure changed to this::
|
||||
|
||||
yourpackage/
|
||||
main.py
|
||||
utils.py
|
||||
scripts/
|
||||
yourscript.py
|
||||
|
||||
In this case instead of using ``py_modules`` in your ``setup.py`` file you
|
||||
can use ``packages`` and the automatic package finding support of
|
||||
setuptools. In addition to that it's also recommended to include other
|
||||
package data.
|
||||
|
||||
These would be the modified contents of ``setup.py``::
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='yourpackage',
|
||||
version='0.1',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
yourscript=yourpackage.scripts.yourscript:cli
|
||||
''',
|
||||
)
|
90
docs/testing.rst
Normal file
|
@ -0,0 +1,90 @@
|
|||
Testing Click Applications
|
||||
==========================
|
||||
|
||||
.. currentmodule:: click.testing
|
||||
|
||||
For basic testing, Click provides the :mod:`click.testing` module which
|
||||
provides test functionality that helps you invoke command line
|
||||
applications and check their behavior.
|
||||
|
||||
These tools should really only be used for testing as they change
|
||||
the entire interpreter state for simplicity and are not in any way
|
||||
thread-safe!
|
||||
|
||||
Basic Testing
|
||||
-------------
|
||||
|
||||
The basic functionality for testing Click applications is the
|
||||
:class:`CliRunner` which can invoke commands as command line scripts. The
|
||||
:meth:`CliRunner.invoke` method runs the command line script in isolation
|
||||
and captures the output as both bytes and binary data.
|
||||
|
||||
The return value is a :class:`Result` object, which has the captured output
|
||||
data, exit code, and optional exception attached.
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
|
||||
def test_hello_world():
|
||||
@click.command()
|
||||
@click.argument('name')
|
||||
def hello(name):
|
||||
click.echo('Hello %s!' % name)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(hello, ['Peter'])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'Hello Peter!\n'
|
||||
|
||||
File System Isolation
|
||||
---------------------
|
||||
|
||||
For basic command line tools that want to operate with the file system, the
|
||||
:meth:`CliRunner.isolated_filesystem` method comes in useful which sets up
|
||||
an empty folder and changes the current working directory to.
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
|
||||
def test_cat():
|
||||
@click.command()
|
||||
@click.argument('f', type=click.File())
|
||||
def cat(f):
|
||||
click.echo(f.read())
|
||||
|
||||
runner = CliRunner()
|
||||
with runner.isolated_filesystem():
|
||||
with open('hello.txt') as f:
|
||||
f.write('Hello World!')
|
||||
|
||||
result = runner.invoke(cat, ['hello.txt'])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'Hello World!\n'
|
||||
|
||||
Input Streams
|
||||
-------------
|
||||
|
||||
The test wrapper can also be used to provide input data for the input
|
||||
stream (stdin). This is very useful for testing prompts, for instance::
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
|
||||
def test_prompts():
|
||||
@click.command()
|
||||
@click.option('--foo', prompt=True)
|
||||
def test(foo):
|
||||
click.echo('foo=%s' % foo)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(test, input='wau wau\n')
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo: wau wau\nfoo=wau wau\n'
|
||||
|
||||
Note that prompts will be emulated so that they write the input data to
|
||||
the output stream as well. If hidden input is expected then this
|
||||
obviously does not happen.
|
100
docs/upgrading.rst
Normal file
|
@ -0,0 +1,100 @@
|
|||
Upgrading To Newer Releases
|
||||
===========================
|
||||
|
||||
Click attempts the highest level of backwards compatibility but sometimes
|
||||
this is not entirely possible. In case we need to break backwards
|
||||
compatibility this document gives you information about how to upgrade or
|
||||
handle backwards compatibility properly.
|
||||
|
||||
.. _upgrade-to-3.2:
|
||||
|
||||
Upgrading to 3.2
|
||||
----------------
|
||||
|
||||
Click 3.2 had to perform two changes to multi commands which were
|
||||
triggered by a change between Click 2 and Click 3 that had bigger
|
||||
consequences than anticipated.
|
||||
|
||||
Context Invokes
|
||||
```````````````
|
||||
|
||||
Click 3.2 contains a fix for the :meth:`Context.invoke` function when used
|
||||
with other commands. The original intention of this function was to
|
||||
invoke the other command as as if it came from the command line when it
|
||||
was passed a context object instead of a function. This use was only
|
||||
documented in a single place in the documentation before and there was no
|
||||
proper explanation for the method in the API documentation.
|
||||
|
||||
The core issue is that before 3.2 this call worked against intentions::
|
||||
|
||||
ctx.invoke(other_command, 'arg1', 'arg2')
|
||||
|
||||
This was never intended to work as it does not allow Click to operate on
|
||||
the parameters. Given that this pattern was never documented and ill
|
||||
intended the decision was made to change this behavior in a bugfix release
|
||||
before it spreads by accident and developers depend on it.
|
||||
|
||||
The correct invocation for the above command is the following::
|
||||
|
||||
ctx.invoke(other_command, name_of_arg1='arg1', name_of_arg2='arg2')
|
||||
|
||||
This also allowed us to fix the issue that defaults were not handled
|
||||
properly by this function.
|
||||
|
||||
Multicommand Chaining API
|
||||
`````````````````````````
|
||||
|
||||
Click 3 introduced multicommand chaning. This required a change in how
|
||||
Click internally dispatches. Unfortunately this change was not correctly
|
||||
implemented and it appeared that it was possible to provide an API that
|
||||
can inform the super command about all the subcommands that will be
|
||||
invoked.
|
||||
|
||||
This assumption however does not work with one of the API guarantees that
|
||||
have been given in the past. As such this functionality has been removed
|
||||
in 3.2 as it was already broken. Instead the accidentally broken
|
||||
functionality of the :attr:`Context.invoked_subcommand` attribute was
|
||||
restored.
|
||||
|
||||
If you do require the know which exact commands will be invoked there are
|
||||
different ways to cope with this. The first one is to let the subcommands
|
||||
all return functions and then to invoke the functions in a
|
||||
:meth:`Context.resultcallback`.
|
||||
|
||||
|
||||
.. _upgrade-to-2.0:
|
||||
|
||||
Upgrading to 2.0
|
||||
----------------
|
||||
|
||||
Click 2.0 has one breaking change which is the signature for parameter
|
||||
callbacks. Before 2.0, the callback was invoked with ``(ctx, value)``
|
||||
whereas now it's ``(ctx, param, value)``. This change was necessary as it
|
||||
otherwise made reusing callbacks too complicated.
|
||||
|
||||
To ease the transition Click will still accept old callbacks. Starting
|
||||
with Click 3.0 it will start to issue a warning to stderr to encourage you
|
||||
to upgrade.
|
||||
|
||||
In case you want to support both Click 1.0 and Click 2.0, you can make a
|
||||
simple decorator that adjusts the signatures::
|
||||
|
||||
import click
|
||||
from functools import update_wrapper
|
||||
|
||||
def compatcallback(f):
|
||||
# Click 1.0 does not have a version string stored, so we need to
|
||||
# use getattr here to be safe.
|
||||
if getattr(click, '__version__', '0.0') >= '2.0':
|
||||
return f
|
||||
return update_wrapper(lambda ctx, value: f(ctx, None, value), f)
|
||||
|
||||
With that helper you can then write something like this::
|
||||
|
||||
@compatcallback
|
||||
def callback(ctx, param, value):
|
||||
return value.upper()
|
||||
|
||||
Note that because Click 1.0 did not pass a parameter, the `param` argument
|
||||
here would be `None`, so a compatibility callback could not use that
|
||||
argument.
|
368
docs/utils.rst
Normal file
|
@ -0,0 +1,368 @@
|
|||
Utilities
|
||||
=========
|
||||
|
||||
.. currentmodule:: click
|
||||
|
||||
Besides the functionality that Click provides to interface with argument
|
||||
parsing and handling, it also provides a bunch of addon functionality that
|
||||
is useful for writing command line utilities.
|
||||
|
||||
|
||||
Printing to Stdout
|
||||
------------------
|
||||
|
||||
The most obvious helper is the :func:`echo` function, which in many ways
|
||||
works like the Python ``print`` statement or function. The main difference is
|
||||
that it works the same in Python 2 and 3, it intelligently detects
|
||||
misconfigured output streams, and it will never fail (except in Python 3; for
|
||||
more information see :ref:`python3-limitations`).
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
|
||||
click.echo('Hello World!')
|
||||
|
||||
Most importantly, it can print both Unicode and binary data, unlike the
|
||||
built-in ``print`` function in Python 3, which cannot output any bytes. It
|
||||
will, however, emit a trailing newline by default, which needs to be
|
||||
suppressed by passing ``nl=False``::
|
||||
|
||||
click.echo(b'\xe2\x98\x83', nl=False)
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
Starting with Click 3.0 you can also easily print to standard error by
|
||||
passing ``err=True``::
|
||||
|
||||
click.echo('Hello World!', err=True)
|
||||
|
||||
|
||||
.. _ansi-colors:
|
||||
|
||||
ANSI Colors
|
||||
-----------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Starting with Click 2.0, the :func:`echo` function gained extra
|
||||
functionality to deal with ANSI colors and styles. Note that on Windows,
|
||||
this functionality is only available if `colorama`_ is installed. If it
|
||||
is installed, then ANSI codes are intelligently handled.
|
||||
|
||||
Primarily this means that:
|
||||
|
||||
- Click's :func:`echo` function will automatically strip ANSI color codes
|
||||
if the stream is not connected to a terminal.
|
||||
- the :func:`echo` function will transparently connect to the terminal on
|
||||
Windows and translate ANSI codes to terminal API calls. This means
|
||||
that colors will work on Windows the same way they do on other
|
||||
operating systems.
|
||||
|
||||
Note for `colorama` support: Click will automatically detect when `colorama`
|
||||
is available and use it. Do *not* call ``colorama.init()``!
|
||||
|
||||
To install `colorama`, run this command::
|
||||
|
||||
$ pip install colorama
|
||||
|
||||
For styling a string, the :func:`style` function can be used::
|
||||
|
||||
import click
|
||||
|
||||
click.echo(click.style('Hello World!', fg='green'))
|
||||
click.echo(click.style('Some more text', bg='blue', fg='white'))
|
||||
click.echo(click.style('ATTENTION', blink=True, bold=True))
|
||||
|
||||
The combination of :func:`echo` and :func:`style` is also available in
|
||||
a single function called :func:`secho`::
|
||||
|
||||
click.secho('Hello World!', fg='green')
|
||||
click.secho('Some more text', bg='blue', fg='white')
|
||||
click.secho('ATTENTION', blink=True, bold=True)
|
||||
|
||||
|
||||
.. _colorama: https://pypi.python.org/pypi/colorama
|
||||
|
||||
Pager Support
|
||||
-------------
|
||||
|
||||
In some situations, you might want to show long texts on the terminal and
|
||||
let a user scroll through it. This can be achieved by using the
|
||||
:func:`echo_via_pager` function which works similarly to the :func:`echo`
|
||||
function, but always writes to stdout and, if possible, through a pager.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
def less():
|
||||
click.echo_via_pager('\n'.join('Line %d' % idx
|
||||
for idx in range(200)))
|
||||
|
||||
|
||||
Screen Clearing
|
||||
---------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
To clear the terminal screen, you can use the :func:`clear` function that
|
||||
is provided starting with Click 2.0. It does what the name suggests: it
|
||||
clears the entire visible screen in a platform-agnostic way:
|
||||
|
||||
::
|
||||
|
||||
import click
|
||||
click.clear()
|
||||
|
||||
|
||||
Getting Characters from Terminal
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Normally, when reading input from the terminal, you would read from
|
||||
standard input. However, this is buffered input and will not show up until
|
||||
the line has been terminated. In certain circumstances, you might not want
|
||||
to do that and instead read individual characters as they are being written.
|
||||
|
||||
For this, Click provides the :func:`getchar` function which reads a single
|
||||
character from the terminal buffer and returns it as a Unicode character.
|
||||
|
||||
Note that this function will always read from the terminal, even if stdin
|
||||
is instead a pipe.
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
|
||||
click.echo('Continue? [yn] ', nl=False)
|
||||
c = click.getchar()
|
||||
click.echo()
|
||||
if c == 'y':
|
||||
click.echo('We will go on')
|
||||
elif c == 'n':
|
||||
click.echo('Abort!')
|
||||
else:
|
||||
click.echo('Invalid input :(')
|
||||
|
||||
Note that this reads raw input, which means that things like arrow keys
|
||||
will show up in the platform's native escape format. The only characters
|
||||
translated are ``^C`` and ``^D`` which are converted into keyboard
|
||||
interrupts and end of file exceptions respectively. This is done because
|
||||
otherwise, it's too easy to forget about that and to create scripts that
|
||||
cannot be properly exited.
|
||||
|
||||
|
||||
Waiting for Key Press
|
||||
---------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Sometimes, it's useful to pause until the user presses any key on the
|
||||
keyboard. This is especially useful on Windows where ``cmd.exe`` will
|
||||
close the window at the end of the command execution by default, instead
|
||||
of waiting.
|
||||
|
||||
In click, this can be accomplished with the :func:`pause` function. This
|
||||
function will print a quick message to the terminal (which can be
|
||||
customized) and wait for the user to press a key. In addition to that,
|
||||
it will also become a NOP (no operation instruction) if the script is not
|
||||
run interactively.
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
click.pause()
|
||||
|
||||
|
||||
Launching Editors
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Click supports launching editors automatically through :func:`edit`. This
|
||||
is very useful for asking users for multi-line input. It will
|
||||
automatically open the user's defined editor or fall back to a sensible
|
||||
default. If the user closes the editor without saving, the return value
|
||||
will be `None` otherwise the entered text.
|
||||
|
||||
Example usage::
|
||||
|
||||
import click
|
||||
|
||||
def get_commit_message():
|
||||
MARKER = '# Everything below is ignored\n'
|
||||
message = click.edit('\n\n' + MARKER)
|
||||
if message is not None:
|
||||
return message.split(MARKER, 1)[0].rstrip('\n')
|
||||
|
||||
Alternatively, the function can also be used to launch editors for files by
|
||||
a specific filename. In this case, the return value is always `None`.
|
||||
|
||||
Example usage::
|
||||
|
||||
import click
|
||||
click.edit(filename='/etc/passwd')
|
||||
|
||||
|
||||
Launching Applications
|
||||
----------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Click supports launching applications through :func:`launch`. This can be
|
||||
used to open the default application assocated with a URL or filetype.
|
||||
This can be used to launch web browsers or picture viewers, for instance.
|
||||
In addition to this, it can also launch the file manager and automatically
|
||||
select the provided file.
|
||||
|
||||
Example usage::
|
||||
|
||||
click.launch('http://click.pocoo.org/')
|
||||
click.launch('/my/downloaded/file.txt', locate=True)
|
||||
|
||||
|
||||
Printing Filenames
|
||||
------------------
|
||||
|
||||
Because filenames might not be Unicode, formatting them can be a bit
|
||||
tricky. Generally, this is easier in Python 2 than on 3, as you can just
|
||||
write the bytes to stdout with the ``print`` function, but in Python 3, you
|
||||
will always need to operate in Unicode.
|
||||
|
||||
The way this works with click is through the :func:`format_filename`
|
||||
function. It does a best-effort conversion of the filename to Unicode and
|
||||
will never fail. This makes it possible to use these filenames in the
|
||||
context of a full Unicode string.
|
||||
|
||||
Example::
|
||||
|
||||
click.echo('Path: %s' % click.format_filename(b'foo.txt'))
|
||||
|
||||
|
||||
Standard Streams
|
||||
----------------
|
||||
|
||||
For command line utilities, it's very important to get access to input and
|
||||
output streams reliably. Python generally provides access to these
|
||||
streams through ``sys.stdout`` and friends, but unfortunately, there are
|
||||
API differences between 2.x and 3.x, especially with regards to how these
|
||||
streams respond to Unicode and binary data.
|
||||
|
||||
Because of this, click provides the :func:`get_binary_stream` and
|
||||
:func:`get_text_stream` functions, which produce consistent results with
|
||||
different Python versions and for a wide variety pf terminal configurations.
|
||||
|
||||
The end result is that these functions will always return a functional
|
||||
stream object (except in very odd cases in Python 3; see
|
||||
:ref:`python3-limitations`).
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
|
||||
stdin_text = click.get_text_stream('stdin')
|
||||
stdout_binary = click.get_binary_stream('stdout')
|
||||
|
||||
|
||||
Intelligent File Opening
|
||||
------------------------
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
Starting with Click 3.0 the logic for opening files from the :class:`File`
|
||||
type is exposed through the :func:`open_file` function. It can
|
||||
intelligently open stdin/stdout as well as any other file.
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
|
||||
stdout = click.open_file('-', 'w')
|
||||
test_file = click.open_file('test.txt', 'w')
|
||||
|
||||
If stdin or stdout are returned, the return value is wrapped in a special
|
||||
file where the context manager will prevent the closing of the file. This
|
||||
makes the handling of standard streams transparent and you can always use
|
||||
it like this::
|
||||
|
||||
with click.open_file(filename, 'w') as f:
|
||||
f.write('Hello World!\n')
|
||||
|
||||
|
||||
Finding Application Folders
|
||||
---------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Very often, you want to open a configuration file that belongs to your
|
||||
application. However, different operating systems store these configuration
|
||||
files in different locations depending on their standards. Click provides
|
||||
a :func:`get_app_dir` function which returns the most appropriate location
|
||||
for per-user config files for your application depending on the OS.
|
||||
|
||||
Example usage::
|
||||
|
||||
import os
|
||||
import click
|
||||
import ConfigParser
|
||||
|
||||
APP_NAME = 'My Application'
|
||||
|
||||
def read_config():
|
||||
cfg = os.path.join(click.get_app_dir(APP_NAME), 'config.ini')
|
||||
parser = ConfigParser.RawConfigParser()
|
||||
parser.read([cfg])
|
||||
rv = {}
|
||||
for section in parser.sections():
|
||||
for key, value in parser.items(section):
|
||||
rv['%s.%s' % (section, key)] = value
|
||||
return rv
|
||||
|
||||
|
||||
Showing Progress Bars
|
||||
---------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Sometimes, you have command line scripts that need to process a lot of data,
|
||||
but you want to quickly show the user some progress about how long that
|
||||
will take. Click supports simple progress bar rendering for that through
|
||||
the :func:`progressbar` function.
|
||||
|
||||
The basic usage is very simple: the idea is that you have an iterable that
|
||||
you want to operate on. For each item in the iterable it might take some
|
||||
time to do processing. So say you have a loop like this::
|
||||
|
||||
for user in all_the_users_to_process:
|
||||
modify_the_user(user)
|
||||
|
||||
To hook this up with an automatically updating progress bar, all you need
|
||||
to do is to change the code to this::
|
||||
|
||||
import click
|
||||
|
||||
with click.progressbar(all_the_users_to_process) as bar:
|
||||
for user in bar:
|
||||
modify_the_user(user)
|
||||
|
||||
Click will then automatically print a progress bar to the terminal and
|
||||
calculate the remaining time for you. The calculation of remaining time
|
||||
requires that the iterable has a length. If it does not have a length
|
||||
but you know the length, you can explicitly provide it::
|
||||
|
||||
with click.progressbar(all_the_users_to_process,
|
||||
length=number_of_users) as bar:
|
||||
for user in bar:
|
||||
modify_the_user(user)
|
||||
|
||||
Another useful feature is to associate a label with the progress bar which
|
||||
will be shown preceding the progress bar::
|
||||
|
||||
with click.progressbar(all_the_users_to_process,
|
||||
label='Modifying user accounts',
|
||||
length=number_of_users) as bar:
|
||||
for user in bar:
|
||||
modify_the_user(user)
|
152
docs/why.rst
Normal file
|
@ -0,0 +1,152 @@
|
|||
Why Click?
|
||||
==========
|
||||
|
||||
There are so many libraries out there for writing command line utilities;
|
||||
why does Click exist?
|
||||
|
||||
This question is easy to answer: because there is not a single command
|
||||
line utility for Python out there which ticks the following boxes:
|
||||
|
||||
* is lazily composable without restrictions
|
||||
* fully follows the Unix command line conventions
|
||||
* supports loading values from environment variables out of the box
|
||||
* supports for prompting of custom values
|
||||
* is fully nestable and composable
|
||||
* works the same in Python 2 and 3
|
||||
* supports file handling out of the box
|
||||
* comes with useful common helpers (getting terminal dimensions,
|
||||
ANSI colors, fetching direct keyboard input, screen clearing,
|
||||
finding config paths, launching apps and editors, etc.)
|
||||
|
||||
There are many alternatives to Click and you can have a look at them if
|
||||
you enjoy them better. The obvious ones are ``optparse`` and ``argparse``
|
||||
from the standard library.
|
||||
|
||||
Click is actually implemented as a wrapper around a mild fork of
|
||||
``optparse`` and does not implement any parsing itself. The reason it's
|
||||
not based on ``argparse`` is that ``argparse`` does not allow proper
|
||||
nesting of commands by design and has some deficiencies when it comes to
|
||||
POSIX compliant argument handling.
|
||||
|
||||
Click is designed to be fun to work with and at the same time not stand in
|
||||
your way. It's not overly flexible either. Currently, for instance, it
|
||||
does not allow you to customize the help pages too much. This is intentional
|
||||
because Click is designed to allow you to nest command line utilities. The
|
||||
idea is that you can have a system that works together with another system by
|
||||
tacking two Click instances together and they will continue working as they
|
||||
should.
|
||||
|
||||
Too much customizability would break this promise.
|
||||
|
||||
Click was written to support the `Flask <http://flask.pocoo.org/>`_
|
||||
microframework ecosystem because no tool could provide it with the
|
||||
functionality it needed.
|
||||
|
||||
To get an understanding of what Click is all about, I strongly recommend
|
||||
looking at the :ref:`complex-guide` chapter to see what it's useful for.
|
||||
|
||||
Why not Argparse?
|
||||
-----------------
|
||||
|
||||
Click is internally based on optparse instead of argparse. This however
|
||||
is an implementation detail that a user does not have to be concerned
|
||||
with. The reason however Click is not using argparse is that it has some
|
||||
problematic behaviors that make handling arbitrary command line interfaces
|
||||
hard:
|
||||
|
||||
* argparse has built-in magic behavior to guess if something is an
|
||||
argument or an option. This becomes a problem when dealing with
|
||||
incomplete command lines as it's not possible to know without having a
|
||||
full understanding of the command line how the parser is going to
|
||||
behave. This goes against Click's ambitions of dispatching to
|
||||
subparsers.
|
||||
* argparse currently does not support disabling of interspearsed
|
||||
arguments. Without this feature it's not possible to safely implement
|
||||
Click's nested parsing nature.
|
||||
|
||||
Why not Docopt etc.?
|
||||
--------------------
|
||||
|
||||
Docopt and many tools like it are cool in how they work, but very few of
|
||||
these tools deal with nesting of commands and composability in a way like
|
||||
Click. To the best of the developer's knowledge, Click is the first
|
||||
Python library that aims to create a level of composability of applications
|
||||
that goes beyond what the system itself supports.
|
||||
|
||||
Docopt, for instance, acts by parsing your help pages and then parsing
|
||||
according to those rules. The side effect of this is that docopt is quite
|
||||
rigid in how it handles the command line interface. The upside of docopt
|
||||
is that it gives you strong control over your help page; the downside is
|
||||
that due to this it cannot rewrap your output for the current terminal
|
||||
width and it makes translations hard. On top of that docopt is restricted
|
||||
to basic parsing. It does not handle argument dispatching and callback
|
||||
invocation or types. This means there is a lot of code that needs to be
|
||||
written in addition to the basic help page to handle the parsing results.
|
||||
|
||||
Most of all, however, it makes composability hard. While docopt does
|
||||
support dispatching to subcommands, it for instance does not directly
|
||||
support any kind of automatic subcommand enumeration based on what's
|
||||
available or it does not enforce subcommands to work in a consistent way.
|
||||
|
||||
This is fine, but it's different from how Click wants to work. Click aims
|
||||
to support fully composable command line user interfaces by doing the
|
||||
following:
|
||||
|
||||
- Click does not just parse, it also dispatches to the appropriate code.
|
||||
- Click has a strong concept of an invocation context that allows
|
||||
subcommands to respond to data from the parent command.
|
||||
- Click has strong information available for all parameters and commands
|
||||
so that it can generate unified help pages for the full CLI and to
|
||||
assist the user in converting the input data as necessary.
|
||||
- Click has a strong understanding of what types are and can give the user
|
||||
consistent error messages if something goes wrong. A subcommand
|
||||
written by a different developer will not suddenly die with a
|
||||
different error messsage because it's manually handled.
|
||||
- Click has enough meta information available for its whole program
|
||||
that it can evolve over time to improve the user experience without
|
||||
forcing developers to adjust their programs. For instance, if Click
|
||||
decides to change how help pages are formatted, all Click programs
|
||||
will automatically benefit from this.
|
||||
|
||||
The aim of Click is to make composable systems, whereas the aim of docopt
|
||||
is to build the most beautiful and hand-crafted command line interfaces.
|
||||
These two goals conflict with one another in subtle ways. Click
|
||||
actively prevents people from implementing certain patterns in order to
|
||||
achieve unified command line interfaces. You have very little input on
|
||||
reformatting your help pages for instance.
|
||||
|
||||
|
||||
Why Hardcoded Behaviors?
|
||||
------------------------
|
||||
|
||||
The other question is why Click goes away from optparse and hardcodes
|
||||
certain behaviors instead of staying configurable. There are multiple
|
||||
reasons for this. The biggest one is that too much configurability makes
|
||||
it hard to achieve a consistent command line experience.
|
||||
|
||||
The best example for this is optparse's ``callback`` functionality for
|
||||
accepting arbitrary number of arguments. Due to syntactical ambiguities
|
||||
on the command line, there is no way to implement fully variadic arguments.
|
||||
There are always tradeoffs that need to be made and in case of
|
||||
``argparse`` these tradeoffs have been critical enough, that a system like
|
||||
Click cannot even be implemented on top of it.
|
||||
|
||||
In this particular case, Click attempts to stay with a handful of accepted
|
||||
paradigms for building command line interfaces that can be well documented
|
||||
and tested.
|
||||
|
||||
|
||||
Why No Auto Correction?
|
||||
-----------------------
|
||||
|
||||
The question came up why Click does not auto correct parameters given that
|
||||
even optparse and argparse support automatic expansion of long arguments.
|
||||
The reason for this is that it's a liability for backwards compatibility.
|
||||
If people start relying on automatically modified parameters and someone
|
||||
adds a new parameter in the future, the script might stop working. These
|
||||
kinds of problems are hard to find so Click does not attempt to be magical
|
||||
about this.
|
||||
|
||||
This sort of behavior however can be implemented on a higher level to
|
||||
support things such as explicit aliases. For more information see
|
||||
:ref:`aliases`.
|
BIN
examples/.DS_Store
vendored
Normal file
12
examples/README
Normal file
|
@ -0,0 +1,12 @@
|
|||
Click Examples
|
||||
|
||||
This folder contains various Click examples. Note that
|
||||
all of these are not runnable by themselves but should be
|
||||
installed into a virtualenv.
|
||||
|
||||
This is done this way so that scripts also properly work
|
||||
on Windows and in virtualenvs without accidentally executing
|
||||
through the wrong interpreter.
|
||||
|
||||
For more information about this see the documentation:
|
||||
http://click.pocoo.org/setuptools/
|
17
examples/aliases/README
Normal file
|
@ -0,0 +1,17 @@
|
|||
$ aliases_
|
||||
|
||||
aliases is a fairly advanced example that shows how
|
||||
to implement command aliases with Click. It uses a
|
||||
subclass of the default group to customize how commands
|
||||
are located.
|
||||
|
||||
It supports both aliases read from a config file as well
|
||||
as automatic abbreviations.
|
||||
|
||||
The aliases from the config are read from the aliases.ini
|
||||
file. Try `aliases st` and `aliases ci`!
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ aliases --help
|
2
examples/aliases/aliases.ini
Normal file
|
@ -0,0 +1,2 @@
|
|||
[aliases]
|
||||
ci=commit
|
111
examples/aliases/aliases.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import os
|
||||
import click
|
||||
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
except ImportError:
|
||||
import configparser
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""The config in this example only holds aliases."""
|
||||
|
||||
def __init__(self):
|
||||
self.path = os.getcwd()
|
||||
self.aliases = {}
|
||||
|
||||
def read_config(self, filename):
|
||||
parser = configparser.RawConfigParser()
|
||||
parser.read([filename])
|
||||
try:
|
||||
self.aliases.update(parser.items('aliases'))
|
||||
except configparser.NoSectionError:
|
||||
pass
|
||||
|
||||
|
||||
pass_config = click.make_pass_decorator(Config, ensure=True)
|
||||
|
||||
|
||||
class AliasedGroup(click.Group):
|
||||
"""This subclass of a group supports looking up aliases in a config
|
||||
file and with a bit of magic.
|
||||
"""
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
# Step one: bulitin commands as normal
|
||||
rv = click.Group.get_command(self, ctx, cmd_name)
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
# Step two: find the config object and ensure it's there. This
|
||||
# will create the config object is missing.
|
||||
cfg = ctx.ensure_object(Config)
|
||||
|
||||
# Step three: lookup an explicit command aliase in the config
|
||||
if cmd_name in cfg.aliases:
|
||||
actual_cmd = cfg.aliases[cmd_name]
|
||||
return click.Group.get_command(self, ctx, actual_cmd)
|
||||
|
||||
# Alternative option: if we did not find an explicit alias we
|
||||
# allow automatic abbreviation of the command. "status" for
|
||||
# instance will match "st". We only allow that however if
|
||||
# there is only one command.
|
||||
matches = [x for x in self.list_commands(ctx)
|
||||
if x.lower().startswith(cmd_name.lower())]
|
||||
if not matches:
|
||||
return None
|
||||
elif len(matches) == 1:
|
||||
return click.Group.get_command(self, ctx, matches[0])
|
||||
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
|
||||
|
||||
|
||||
def read_config(ctx, param, value):
|
||||
"""Callback that is used whenever --config is passed. We use this to
|
||||
always load the correct config. This means that the config is loaded
|
||||
even if the group itself never executes so our aliases stay always
|
||||
available.
|
||||
"""
|
||||
cfg = ctx.ensure_object(Config)
|
||||
if value is None:
|
||||
value = os.path.join(os.path.dirname(__file__), 'aliases.ini')
|
||||
cfg.read_config(value)
|
||||
return value
|
||||
|
||||
|
||||
@click.command(cls=AliasedGroup)
|
||||
@click.option('--config', type=click.Path(exists=True, dir_okay=False),
|
||||
callback=read_config, expose_value=False,
|
||||
help='The config file to use instead of the default.')
|
||||
def cli():
|
||||
"""An example application that supports aliases."""
|
||||
|
||||
|
||||
@cli.command()
|
||||
def push():
|
||||
"""Pushes changes."""
|
||||
click.echo('Push')
|
||||
|
||||
|
||||
@cli.command()
|
||||
def pull():
|
||||
"""Pulls changes."""
|
||||
click.echo('Pull')
|
||||
|
||||
|
||||
@cli.command()
|
||||
def clone():
|
||||
"""Clones a repository."""
|
||||
click.echo('Clone')
|
||||
|
||||
|
||||
@cli.command()
|
||||
def commit():
|
||||
"""Commits pending changes."""
|
||||
click.echo('Commit')
|
||||
|
||||
|
||||
@cli.command()
|
||||
@pass_config
|
||||
def status(config):
|
||||
"""Shows the status."""
|
||||
click.echo('Status for %s' % config.path)
|
15
examples/aliases/setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-aliases',
|
||||
version='1.0',
|
||||
py_modules=['aliases'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
aliases=aliases:cli
|
||||
''',
|
||||
)
|
11
examples/colors/README
Normal file
|
@ -0,0 +1,11 @@
|
|||
$ colors_
|
||||
|
||||
colors is a simple example that shows how you can
|
||||
colorize text.
|
||||
|
||||
For this to work on Windows, colorama is required.
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ colors
|
26
examples/colors/colors.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import click
|
||||
|
||||
|
||||
all_colors = 'black', 'red', 'green', 'yellow', 'blue', 'magenta', \
|
||||
'cyan', 'white'
|
||||
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""This script prints some colors. If colorama is installed this will
|
||||
also work on Windows. It will also automatically remove all ANSI
|
||||
styles if data is piped into a file.
|
||||
|
||||
Give it a try!
|
||||
"""
|
||||
for color in all_colors:
|
||||
click.echo(click.style('I am colored %s' % color, fg=color))
|
||||
for color in all_colors:
|
||||
click.echo(click.style('I am colored %s and bold' % color,
|
||||
fg=color, bold=True))
|
||||
for color in all_colors:
|
||||
click.echo(click.style('I am reverse colored %s' % color, fg=color,
|
||||
reverse=True))
|
||||
|
||||
click.echo(click.style('I am blinking', blink=True))
|
||||
click.echo(click.style('I am underlined', underline=True))
|
17
examples/colors/setup.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-colors',
|
||||
version='1.0',
|
||||
py_modules=['colors'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
# Colorama is only required for Windows.
|
||||
'colorama',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
colors=colors:cli
|
||||
''',
|
||||
)
|
16
examples/complex/README
Normal file
|
@ -0,0 +1,16 @@
|
|||
$ complex_
|
||||
|
||||
complex is an example of building very complex cli
|
||||
applications that load subcommands dynamically from
|
||||
a plugin folder and other things.
|
||||
|
||||
All the commands are implemented as plugins in the
|
||||
`complex.commands` package. If a python module is
|
||||
placed named "cmd_foo" it will show up as "foo"
|
||||
command and the `cli` object within it will be
|
||||
loaded as nested Click command.
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ complex --help
|
0
examples/complex/complex/__init__.py
Normal file
65
examples/complex/complex/cli.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
import os
|
||||
import sys
|
||||
import click
|
||||
|
||||
|
||||
CONTEXT_SETTINGS = dict(auto_envvar_prefix='COMPLEX')
|
||||
|
||||
|
||||
class Context(object):
|
||||
|
||||
def __init__(self):
|
||||
self.verbose = False
|
||||
self.home = os.getcwd()
|
||||
|
||||
def log(self, msg, *args):
|
||||
"""Logs a message to stderr."""
|
||||
if args:
|
||||
msg %= args
|
||||
click.echo(msg, file=sys.stderr)
|
||||
|
||||
def vlog(self, msg, *args):
|
||||
"""Logs a message to stderr only if verbose is enabled."""
|
||||
if self.verbose:
|
||||
self.log(msg, *args)
|
||||
|
||||
|
||||
pass_context = click.make_pass_decorator(Context, ensure=True)
|
||||
cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'commands'))
|
||||
|
||||
|
||||
class ComplexCLI(click.MultiCommand):
|
||||
|
||||
def list_commands(self, ctx):
|
||||
rv = []
|
||||
for filename in os.listdir(cmd_folder):
|
||||
if filename.endswith('.py') and \
|
||||
filename.startswith('cmd_'):
|
||||
rv.append(filename[4:-3])
|
||||
rv.sort()
|
||||
return rv
|
||||
|
||||
def get_command(self, ctx, name):
|
||||
try:
|
||||
if sys.version_info[0] == 2:
|
||||
name = name.encode('ascii', 'replace')
|
||||
mod = __import__('complex.commands.cmd_' + name,
|
||||
None, None, ['cli'])
|
||||
except ImportError:
|
||||
return
|
||||
return mod.cli
|
||||
|
||||
|
||||
@click.command(cls=ComplexCLI, context_settings=CONTEXT_SETTINGS)
|
||||
@click.option('--home', type=click.Path(exists=True, file_okay=False,
|
||||
resolve_path=True),
|
||||
help='Changes the folder to operate on.')
|
||||
@click.option('-v', '--verbose', is_flag=True,
|
||||
help='Enables verbose mode.')
|
||||
@pass_context
|
||||
def cli(ctx, verbose, home):
|
||||
"""A complex command line interface."""
|
||||
ctx.verbose = verbose
|
||||
if home is not None:
|
||||
ctx.home = home
|
0
examples/complex/complex/commands/__init__.py
Normal file
13
examples/complex/complex/commands/cmd_init.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import click
|
||||
from complex.cli import pass_context
|
||||
|
||||
|
||||
@click.command('init', short_help='Initializes a repo.')
|
||||
@click.argument('path', required=False, type=click.Path(resolve_path=True))
|
||||
@pass_context
|
||||
def cli(ctx, path):
|
||||
"""Initializes a repository."""
|
||||
if path is None:
|
||||
path = ctx.home
|
||||
ctx.log('Initialized the repository in %s',
|
||||
click.format_filename(path))
|
10
examples/complex/complex/commands/cmd_status.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import click
|
||||
from complex.cli import pass_context
|
||||
|
||||
|
||||
@click.command('status', short_help='Shows file changes.')
|
||||
@pass_context
|
||||
def cli(ctx):
|
||||
"""Shows file changes in the current working directory."""
|
||||
ctx.log('Changed files: none')
|
||||
ctx.vlog('bla bla bla, debug info')
|
15
examples/complex/setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-complex',
|
||||
version='1.0',
|
||||
packages=['complex', 'complex.commands'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
complex=complex.cli:cli
|
||||
''',
|
||||
)
|
BIN
examples/imagepipe/.DS_Store
vendored
Normal file
1
examples/imagepipe/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
processed-*
|
13
examples/imagepipe/README
Normal file
|
@ -0,0 +1,13 @@
|
|||
$ imagepipe_
|
||||
|
||||
imagepipe is an example application that implements some
|
||||
multi commands that chain image processing instructions
|
||||
together.
|
||||
|
||||
This requires pillow.
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ imagepipe open -i example01.jpg resize -w 128 display
|
||||
$ imagepipe open -i example02.jpg blur save
|
10
examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,10 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: click-example-imagepipe
|
||||
Version: 1.0
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
||||
Author-email: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,8 @@
|
|||
README
|
||||
imagepipe.py
|
||||
click_example_imagepipe.egg-info/PKG-INFO
|
||||
click_example_imagepipe.egg-info/SOURCES.txt
|
||||
click_example_imagepipe.egg-info/dependency_links.txt
|
||||
click_example_imagepipe.egg-info/entry_points.txt
|
||||
click_example_imagepipe.egg-info/requires.txt
|
||||
click_example_imagepipe.egg-info/top_level.txt
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[console_scripts]
|
||||
imagepipe=imagepipe:cli
|
||||
|
|
@ -0,0 +1 @@
|
|||
Click
|
|
@ -0,0 +1 @@
|
|||
imagepipe
|
BIN
examples/imagepipe/example01.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
examples/imagepipe/example02.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
266
examples/imagepipe/imagepipe.py
Normal file
|
@ -0,0 +1,266 @@
|
|||
import click
|
||||
from functools import update_wrapper
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
|
||||
|
||||
@click.group(chain=True)
|
||||
def cli():
|
||||
"""This script processes a bunch of images through pillow in a unix
|
||||
pipe. One commands feeds into the next.
|
||||
|
||||
Example:
|
||||
|
||||
\b
|
||||
imagepipe open -i example01.jpg resize -w 128 display
|
||||
imagepipe open -i example02.jpg blur save
|
||||
"""
|
||||
|
||||
|
||||
@cli.resultcallback()
|
||||
def process_commands(processors):
|
||||
"""This result callback is invoked with an iterable of all the chained
|
||||
subcommands. As in this example each subcommand returns a function
|
||||
we can chain them together to feed one into the other, similar to how
|
||||
a pipe on unix works.
|
||||
"""
|
||||
# Start with an empty iterable.
|
||||
stream = ()
|
||||
|
||||
# Pipe it through all stream processors.
|
||||
for processor in processors:
|
||||
stream = processor(stream)
|
||||
|
||||
# Evaluate the stream and throw away the items.
|
||||
for _ in stream:
|
||||
pass
|
||||
|
||||
|
||||
def processor(f):
|
||||
"""Helper decorator to rewrite a function so that it returns another
|
||||
function from it.
|
||||
"""
|
||||
def new_func(*args, **kwargs):
|
||||
def processor(stream):
|
||||
return f(stream, *args, **kwargs)
|
||||
return processor
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def generator(f):
|
||||
"""Similar to the :func:`processor` but passes through old values
|
||||
unchanged and does not pass through the values as parameter.
|
||||
"""
|
||||
@processor
|
||||
def new_func(stream, *args, **kwargs):
|
||||
for item in stream:
|
||||
yield item
|
||||
for item in f(*args, **kwargs):
|
||||
yield item
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def copy_filename(new, old):
|
||||
new.filename = old.filename
|
||||
return new
|
||||
|
||||
|
||||
@cli.command('open')
|
||||
@click.option('-i', '--image', 'images', type=click.Path(),
|
||||
multiple=True, help='The image file to open.')
|
||||
@generator
|
||||
def open_cmd(images):
|
||||
"""Loads one or multiple images for processing. The input parameter
|
||||
can be specified multiple times to load more than one image.
|
||||
"""
|
||||
for image in images:
|
||||
try:
|
||||
click.echo('Opening "%s"' % image)
|
||||
if image == '-':
|
||||
img = Image.open(click.get_binary_stdin())
|
||||
img.filename = '-'
|
||||
else:
|
||||
img = Image.open(image)
|
||||
yield img
|
||||
except Exception as e:
|
||||
click.echo('Could not open image "%s": %s' % (image, e), err=True)
|
||||
|
||||
|
||||
@cli.command('save')
|
||||
@click.option('--filename', default='processed-%04d.png', type=click.Path(),
|
||||
help='The format for the filename.',
|
||||
show_default=True)
|
||||
@processor
|
||||
def save_cmd(images, filename):
|
||||
"""Saves all processed images to a series of files."""
|
||||
for idx, image in enumerate(images):
|
||||
try:
|
||||
fn = filename % (idx + 1)
|
||||
click.echo('Saving "%s" as "%s"' % (image.filename, fn))
|
||||
yield image.save(fn)
|
||||
except Exception as e:
|
||||
click.echo('Could not save image "%s": %s' %
|
||||
(image.filename, e), err=True)
|
||||
|
||||
|
||||
@cli.command('display')
|
||||
@processor
|
||||
def display_cmd(images):
|
||||
"""Opens all images in an image viewer."""
|
||||
for image in images:
|
||||
click.echo('Displaying "%s"' % image.filename)
|
||||
image.show()
|
||||
yield image
|
||||
|
||||
|
||||
@cli.command('resize')
|
||||
@click.option('-w', '--width', type=int, help='The new width of the image.')
|
||||
@click.option('-h', '--height', type=int, help='The new height of the image.')
|
||||
@processor
|
||||
def resize_cmd(images, width, height):
|
||||
"""Resizes an image by fitting it into the box without changing
|
||||
the aspect ratio.
|
||||
"""
|
||||
for image in images:
|
||||
w, h = (width or image.size[0], height or image.size[1])
|
||||
click.echo('Resizing "%s" to %dx%d' % (image.filename, w, h))
|
||||
image.thumbnail((w, h))
|
||||
yield image
|
||||
|
||||
|
||||
@cli.command('crop')
|
||||
@click.option('-b', '--border', type=int, help='Crop the image from all '
|
||||
'sides by this amount.')
|
||||
@processor
|
||||
def crop_cmd(images, border):
|
||||
"""Crops an image from all edges."""
|
||||
for image in images:
|
||||
box = [0, 0, image.size[0], image.size[1]]
|
||||
|
||||
if border is not None:
|
||||
for idx, val in enumerate(box):
|
||||
box[idx] = max(0, val - border)
|
||||
click.echo('Cropping "%s" by %dpx' % (image.filename, border))
|
||||
yield copy_filename(image.crop(box), image)
|
||||
else:
|
||||
yield image
|
||||
|
||||
|
||||
def convert_rotation(ctx, param, value):
|
||||
if value is None:
|
||||
return
|
||||
value = value.lower()
|
||||
if value in ('90', 'r', 'right'):
|
||||
return (Image.ROTATE_90, 90)
|
||||
if value in ('180', '-180'):
|
||||
return (Image.ROTATE_180, 180)
|
||||
if value in ('-90', '270', 'l', 'left'):
|
||||
return (Image.ROTATE_270, 270)
|
||||
raise click.BadParameter('invalid rotation "%s"' % value)
|
||||
|
||||
|
||||
def convert_flip(ctx, param, value):
|
||||
if value is None:
|
||||
return
|
||||
value = value.lower()
|
||||
if value in ('lr', 'leftright'):
|
||||
return (Image.FLIP_LEFT_RIGHT, 'left to right')
|
||||
if value in ('tb', 'topbottom', 'upsidedown', 'ud'):
|
||||
return (Image.FLIP_LEFT_RIGHT, 'top to bottom')
|
||||
raise click.BadParameter('invalid flip "%s"' % value)
|
||||
|
||||
|
||||
@cli.command('transpose')
|
||||
@click.option('-r', '--rotate', callback=convert_rotation,
|
||||
help='Rotates the image (in degrees)')
|
||||
@click.option('-f', '--flip', callback=convert_flip,
|
||||
help='Flips the image [LR / TB]')
|
||||
@processor
|
||||
def transpose_cmd(images, rotate, flip):
|
||||
"""Transposes an image by either rotating or flipping it."""
|
||||
for image in images:
|
||||
if rotate is not None:
|
||||
mode, degrees = rotate
|
||||
click.echo('Rotate "%s" by %ddeg' % (image.filename, degrees))
|
||||
image = copy_filename(image.transpose(mode), image)
|
||||
if flip is not None:
|
||||
mode, direction = flip
|
||||
click.echo('Flip "%s" %s' % (image.filename, direction))
|
||||
image = copy_filename(image.transpose(mode), image)
|
||||
yield image
|
||||
|
||||
|
||||
@cli.command('blur')
|
||||
@click.option('-r', '--radius', default=2, show_default=True,
|
||||
help='The blur radius.')
|
||||
@processor
|
||||
def blur_cmd(images, radius):
|
||||
"""Applies gaussian blur."""
|
||||
blur = ImageFilter.GaussianBlur(radius)
|
||||
for image in images:
|
||||
click.echo('Blurring "%s" by %dpx' % (image.filename, radius))
|
||||
yield copy_filename(image.filter(blur), image)
|
||||
|
||||
|
||||
@cli.command('smoothen')
|
||||
@click.option('-i', '--iterations', default=1, show_default=True,
|
||||
help='How many iterations of the smoothen filter to run.')
|
||||
@processor
|
||||
def smoothen_cmd(images, iterations):
|
||||
"""Applies a smoothening filter."""
|
||||
for image in images:
|
||||
click.echo('Smoothening "%s" %d time%s' %
|
||||
(image.filename, iterations, iterations != 1 and 's' or '',))
|
||||
for x in xrange(iterations):
|
||||
image = copy_filename(image.filter(ImageFilter.BLUR), image)
|
||||
yield image
|
||||
|
||||
|
||||
@cli.command('emboss')
|
||||
@processor
|
||||
def emboss_cmd(images):
|
||||
"""Embosses an image."""
|
||||
for image in images:
|
||||
click.echo('Embossing "%s"' % image.filename)
|
||||
yield copy_filename(image.filter(ImageFilter.EMBOSS), image)
|
||||
|
||||
|
||||
@cli.command('sharpen')
|
||||
@click.option('-f', '--factor', default=2.0,
|
||||
help='Sharpens the image.', show_default=True)
|
||||
@processor
|
||||
def sharpen_cmd(images, factor):
|
||||
"""Sharpens an image."""
|
||||
for image in images:
|
||||
click.echo('Sharpen "%s" by %f' % (image.filename, factor))
|
||||
enhancer = ImageEnhance.Sharpness(image)
|
||||
yield copy_filename(enhancer.enhance(max(1.0, factor)), image)
|
||||
|
||||
|
||||
@cli.command('paste')
|
||||
@click.option('-l', '--left', default=0, help='Offset from left.')
|
||||
@click.option('-r', '--right', default=0, help='Offset from right.')
|
||||
@processor
|
||||
def paste_cmd(images, left, right):
|
||||
"""Pastes the second image on the first image and leaves the rest
|
||||
unchanged.
|
||||
"""
|
||||
imageiter = iter(images)
|
||||
image = next(imageiter, None)
|
||||
to_paste = next(imageiter, None)
|
||||
|
||||
if to_paste is None:
|
||||
if image is not None:
|
||||
yield image
|
||||
return
|
||||
|
||||
click.echo('Paste "%s" on "%s"' %
|
||||
(to_paste.filename, image.filename))
|
||||
mask = None
|
||||
if to_paste.mode == 'RGBA' or 'transparency' in to_paste.info:
|
||||
mask = to_paste
|
||||
image.paste(to_paste, (left, right), mask)
|
||||
image.filename += '+' + to_paste.filename
|
||||
yield image
|
||||
|
||||
for image in imageiter:
|
||||
yield image
|
16
examples/imagepipe/setup.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-imagepipe',
|
||||
version='1.0',
|
||||
py_modules=['imagepipe'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'pillow',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
imagepipe=imagepipe:cli
|
||||
''',
|
||||
)
|
10
examples/inout/README
Normal file
|
@ -0,0 +1,10 @@
|
|||
$ inout_
|
||||
|
||||
inout is a simple example of an application that
|
||||
can read from files and write to files but also
|
||||
accept input from stdin or write to stdout.
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ inout input_file.txt output_file.txt
|
30
examples/inout/inout.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument('input', type=click.File('rb'), nargs=-1)
|
||||
@click.argument('output', type=click.File('wb'))
|
||||
def cli(input, output):
|
||||
"""This script works similar to the Unix `cat` command but it writes
|
||||
into a specific file (which could be the standard output as denoted by
|
||||
the ``-`` sign).
|
||||
|
||||
\b
|
||||
Copy stdin to stdout:
|
||||
inout - -
|
||||
|
||||
\b
|
||||
Copy foo.txt and bar.txt to stdout:
|
||||
inout foo.txt bar.txt -
|
||||
|
||||
\b
|
||||
Write stdin into the file foo.txt
|
||||
inout - foo.txt
|
||||
"""
|
||||
for f in input:
|
||||
while True:
|
||||
chunk = f.read(1024)
|
||||
if not chunk:
|
||||
break
|
||||
output.write(chunk)
|
||||
output.flush()
|
15
examples/inout/setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-inout',
|
||||
version='0.1',
|
||||
py_modules=['inout'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
inout=inout:cli
|
||||
''',
|
||||
)
|
14
examples/naval/README
Normal file
|
@ -0,0 +1,14 @@
|
|||
$ naval_
|
||||
|
||||
naval is a simple example of an application that
|
||||
is ported from the docopt example of the same name.
|
||||
|
||||
Unlike the original this one also runs some code and
|
||||
prints messages and it's command line interface was
|
||||
changed slightly to make more sense with established
|
||||
POSIX semantics.
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ naval --help
|
70
examples/naval/naval.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import click
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option()
|
||||
def cli():
|
||||
"""Naval Fate.
|
||||
|
||||
This is the docopt example adopted to Click but with some actual
|
||||
commands implemented and not just the empty parsing which really
|
||||
is not all that interesting.
|
||||
"""
|
||||
|
||||
|
||||
@cli.group()
|
||||
def ship():
|
||||
"""Manages ships."""
|
||||
|
||||
|
||||
@ship.command('new')
|
||||
@click.argument('name')
|
||||
def ship_new(name):
|
||||
"""Creates a new ship."""
|
||||
click.echo('Created ship %s' % name)
|
||||
|
||||
|
||||
@ship.command('move')
|
||||
@click.argument('ship')
|
||||
@click.argument('x', type=float)
|
||||
@click.argument('y', type=float)
|
||||
@click.option('--speed', metavar='KN', default=10,
|
||||
help='Speed in knots.')
|
||||
def ship_move(ship, x, y, speed):
|
||||
"""Moves SHIP to the new location X,Y."""
|
||||
click.echo('Moving ship %s to %s,%s with speed %s' % (ship, x, y, speed))
|
||||
|
||||
|
||||
@ship.command('shoot')
|
||||
@click.argument('ship')
|
||||
@click.argument('x', type=float)
|
||||
@click.argument('y', type=float)
|
||||
def ship_shoot(ship, x, y):
|
||||
"""Makes SHIP fire to X,Y."""
|
||||
click.echo('Ship %s fires to %s,%s' % (ship, x, y))
|
||||
|
||||
|
||||
@cli.group('mine')
|
||||
def mine():
|
||||
"""Manages mines."""
|
||||
|
||||
|
||||
@mine.command('set')
|
||||
@click.argument('x', type=float)
|
||||
@click.argument('y', type=float)
|
||||
@click.option('ty', '--moored', flag_value='moored',
|
||||
default=True,
|
||||
help='Moored (anchored) mine. Default.')
|
||||
@click.option('ty', '--drifting', flag_value='drifting',
|
||||
help='Drifting mine.')
|
||||
def mine_set(x, y, ty):
|
||||
"""Sets a mine at a specific coordinate."""
|
||||
click.echo('Set %s mine at %s,%s' % (ty, x, y))
|
||||
|
||||
|
||||
@mine.command('remove')
|
||||
@click.argument('x', type=float)
|
||||
@click.argument('y', type=float)
|
||||
def mine_remove(x, y):
|
||||
"""Removes a mine at a specific coordinate."""
|
||||
click.echo('Removed mine at %s,%s' % (x, y))
|
15
examples/naval/setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-naval',
|
||||
version='2.0',
|
||||
py_modules=['naval'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
naval=naval:cli
|
||||
''',
|
||||
)
|
9
examples/repo/README
Normal file
|
@ -0,0 +1,9 @@
|
|||
$ repo_
|
||||
|
||||
repo is a simple example of an application that looks
|
||||
and works similar to hg or git.
|
||||
|
||||
Usage:
|
||||
|
||||
$ pip install --editable .
|
||||
$ repo --help
|
151
examples/repo/repo.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
import os
|
||||
import sys
|
||||
import posixpath
|
||||
|
||||
import click
|
||||
|
||||
|
||||
class Repo(object):
|
||||
|
||||
def __init__(self, home):
|
||||
self.home = home
|
||||
self.config = {}
|
||||
self.verbose = False
|
||||
|
||||
def set_config(self, key, value):
|
||||
self.config[key] = value
|
||||
if self.verbose:
|
||||
click.echo(' config[%s] = %s' % (key, value), file=sys.stderr)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Repo %r>' % self.home
|
||||
|
||||
|
||||
pass_repo = click.make_pass_decorator(Repo)
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option('--repo-home', envvar='REPO_HOME', default='.repo',
|
||||
metavar='PATH', help='Changes the repository folder location.')
|
||||
@click.option('--config', nargs=2, multiple=True,
|
||||
metavar='KEY VALUE', help='Overrides a config key/value pair.')
|
||||
@click.option('--verbose', '-v', is_flag=True,
|
||||
help='Enables verbose mode.')
|
||||
@click.version_option('1.0')
|
||||
@click.pass_context
|
||||
def cli(ctx, repo_home, config, verbose):
|
||||
"""Repo is a command line tool that showcases how to build complex
|
||||
command line interfaces with Click.
|
||||
|
||||
This tool is supposed to look like a distributed version control
|
||||
system to show how something like this can be structured.
|
||||
"""
|
||||
# Create a repo object and remember it as as the context object. From
|
||||
# this point onwards other commands can refer to it by using the
|
||||
# @pass_repo decorator.
|
||||
ctx.obj = Repo(os.path.abspath(repo_home))
|
||||
ctx.obj.verbose = verbose
|
||||
for key, value in config:
|
||||
ctx.obj.set_config(key, value)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('src')
|
||||
@click.argument('dest', required=False)
|
||||
@click.option('--shallow/--deep', default=False,
|
||||
help='Makes a checkout shallow or deep. Deep by default.')
|
||||
@click.option('--rev', '-r', default='HEAD',
|
||||
help='Clone a specific revision instead of HEAD.')
|
||||
@pass_repo
|
||||
def clone(repo, src, dest, shallow, rev):
|
||||
"""Clones a repository.
|
||||
|
||||
This will clone the repository at SRC into the folder DEST. If DEST
|
||||
is not provided this will automatically use the last path component
|
||||
of SRC and create that folder.
|
||||
"""
|
||||
if dest is None:
|
||||
dest = posixpath.split(src)[-1] or '.'
|
||||
click.echo('Cloning repo %s to %s' % (src, os.path.abspath(dest)))
|
||||
repo.home = dest
|
||||
if shallow:
|
||||
click.echo('Making shallow checkout')
|
||||
click.echo('Checking out revision %s' % rev)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.confirmation_option()
|
||||
@pass_repo
|
||||
def delete(repo):
|
||||
"""Deletes a repository.
|
||||
|
||||
This will throw away the current repository.
|
||||
"""
|
||||
click.echo('Destroying repo %s' % repo.home)
|
||||
click.echo('Deleted!')
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--username', prompt=True,
|
||||
help='The developer\'s shown username.')
|
||||
@click.option('--email', prompt='E-Mail',
|
||||
help='The developer\'s email address')
|
||||
@click.password_option(help='The login password.')
|
||||
@pass_repo
|
||||
def setuser(repo, username, email, password):
|
||||
"""Sets the user credentials.
|
||||
|
||||
This will override the current user config.
|
||||
"""
|
||||
repo.set_config('username', username)
|
||||
repo.set_config('email', email)
|
||||
repo.set_config('password', '*' * len(password))
|
||||
click.echo('Changed credentials.')
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--message', '-m', multiple=True,
|
||||
help='The commit message. If provided multiple times each '
|
||||
'argument gets converted into a new line.')
|
||||
@click.argument('files', nargs=-1, type=click.Path())
|
||||
@pass_repo
|
||||
def commit(repo, files, message):
|
||||
"""Commits outstanding changes.
|
||||
|
||||
Commit changes to the given files into the repository. You will need to
|
||||
"repo push" to push up your changes to other repositories.
|
||||
|
||||
If a list of files is omitted, all changes reported by "repo status"
|
||||
will be committed.
|
||||
"""
|
||||
if not message:
|
||||
marker = '# Files to be committed:'
|
||||
hint = ['', '', marker, '#']
|
||||
for file in files:
|
||||
hint.append('# U %s' % file)
|
||||
message = click.edit('\n'.join(hint))
|
||||
if message is None:
|
||||
click.echo('Aborted!')
|
||||
return
|
||||
msg = message.split(marker)[0].rstrip()
|
||||
if not msg:
|
||||
click.echo('Aborted! Empty commit message')
|
||||
return
|
||||
else:
|
||||
msg = '\n'.join(message)
|
||||
click.echo('Files to be committed: %s' % (files,))
|
||||
click.echo('Commit message:\n' + msg)
|
||||
|
||||
|
||||
@cli.command(short_help='Copies files.')
|
||||
@click.option('--force', is_flag=True,
|
||||
help='forcibly copy over an existing managed file')
|
||||
@click.argument('src', nargs=-1, type=click.Path())
|
||||
@click.argument('dst', type=click.Path())
|
||||
@pass_repo
|
||||
def copy(repo, src, dst, force):
|
||||
"""Copies one or multiple files to a new location. This copies all
|
||||
files from SRC to DST.
|
||||
"""
|
||||
for fn in src:
|
||||
click.echo('Copy from %s -> %s' % (fn, dst))
|
15
examples/repo/setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-repo',
|
||||
version='0.1',
|
||||
py_modules=['repo'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
repo=repo:cli
|
||||
''',
|
||||
)
|