New upstream version 7.1.2
This commit is contained in:
parent
0143a0a452
commit
4e974d1c0d
923
CHANGES.rst
923
CHANGES.rst
|
@ -1,5 +1,95 @@
|
|||
Click Changelog
|
||||
===============
|
||||
.. currentmodule:: click
|
||||
|
||||
Version 7.1.2
|
||||
-------------
|
||||
|
||||
Released 2020-04-27
|
||||
|
||||
- Revert applying shell quoting to commands for ``echo_with_pager``
|
||||
and ``edit``. This was intended to allows spaces in commands, but
|
||||
caused issues if the string was actually a command and arguments, or
|
||||
on Windows. Instead, the string must be quoted manually as it should
|
||||
appear on the command line. :issue:`1514`
|
||||
|
||||
|
||||
Version 7.1.1
|
||||
-------------
|
||||
|
||||
Released 2020-03-09
|
||||
|
||||
- Fix ``ClickException`` output going to stdout instead of stderr.
|
||||
:issue:`1495`
|
||||
|
||||
|
||||
Version 7.1
|
||||
-----------
|
||||
|
||||
Released 2020-03-09
|
||||
|
||||
- Fix PyPI package name, "click" is lowercase again.
|
||||
- Fix link in ``unicode_literals`` error message. :pr:`1151`
|
||||
- Add support for colored output on UNIX Jupyter notebooks.
|
||||
:issue:`1185`
|
||||
- Operations that strip ANSI controls will strip the cursor hide/show
|
||||
sequences. :issue:`1216`
|
||||
- Remove unused compat shim for ``bytes``. :pr:`1195`
|
||||
- Expand testing around termui, especially getchar on Windows.
|
||||
:issue:`1116`
|
||||
- Fix output on Windows Python 2.7 built with MSVC 14. :pr:`1342`
|
||||
- Fix ``OSError`` when running in MSYS2. :issue:`1338`
|
||||
- Fix ``OSError`` when redirecting to ``NUL`` stream on Windows.
|
||||
:issue:`1065`
|
||||
- Fix memory leak when parsing Unicode arguments on Windows.
|
||||
:issue:`1136`
|
||||
- Fix error in new AppEngine environments. :issue:`1462`
|
||||
- Always return one of the passed choices for ``click.Choice``
|
||||
:issue:`1277`, :pr:`1318`
|
||||
- Add ``no_args_is_help`` option to ``click.Command``, defaults to
|
||||
False :pr:`1167`
|
||||
- Add ``show_defaults`` parameter to ``Context`` to enable showing
|
||||
defaults globally. :issue:`1018`
|
||||
- Handle ``env MYPATH=''`` as though the option were not passed.
|
||||
:issue:`1196`
|
||||
- It is once again possible to call ``next(bar)`` on an active
|
||||
progress bar instance. :issue:`1125`
|
||||
- ``open_file`` with ``atomic=True`` retains permissions of existing
|
||||
files and respects the current umask for new files. :issue:`1376`
|
||||
- When using the test ``CliRunner`` with ``mix_stderr=False``, if
|
||||
``result.stderr`` is empty it will not raise a ``ValueError``.
|
||||
:issue:`1193`
|
||||
- Remove the unused ``mix_stderr`` parameter from
|
||||
``CliRunner.invoke``. :issue:`1435`
|
||||
- Fix ``TypeError`` raised when using bool flags and specifying
|
||||
``type=bool``. :issue:`1287`
|
||||
- Newlines in option help text are replaced with spaces before
|
||||
re-wrapping to avoid uneven line breaks. :issue:`834`
|
||||
- ``MissingParameter`` exceptions are printable in the Python
|
||||
interpreter. :issue:`1139`
|
||||
- Fix how default values for file-type options are shown during
|
||||
prompts. :issue:`914`
|
||||
- Fix environment variable automatic generation for commands
|
||||
containing ``-``. :issue:`1253`
|
||||
- Option help text replaces newlines with spaces when rewrapping, but
|
||||
preserves paragraph breaks, fixing multiline formatting.
|
||||
:issue:`834, 1066, 1397`
|
||||
- Option help text that is wrapped adds an extra newline at the end to
|
||||
distinguish it from the next option. :issue:`1075`
|
||||
- Consider ``sensible-editor`` when determining the editor to use for
|
||||
``click.edit()``. :pr:`1469`
|
||||
- Arguments to system calls such as the executable path passed to
|
||||
``click.edit`` can contains spaces. :pr:`1470`
|
||||
- Add ZSH completion autoloading and error handling. :issue:`1348`
|
||||
- Add a repr to ``Command``, ``Group``, ``Option``, and ``Argument``,
|
||||
showing the name for friendlier debugging. :issue:`1267`
|
||||
- Completion doesn't consider option names if a value starts with
|
||||
``-`` after the ``--`` separator. :issue:`1247`
|
||||
- ZSH completion escapes special characters in values. :pr:`1418`
|
||||
- Add completion support for Fish shell. :pr:`1423`
|
||||
- Decoding bytes option values falls back to UTF-8 in more cases.
|
||||
:pr:`1468`
|
||||
- Make the warning about old 2-arg parameter callbacks a deprecation
|
||||
warning, to be removed in 8.0. This has been a warning since Click
|
||||
2.0. :pr:`1492`
|
||||
|
||||
|
||||
Version 7.0
|
||||
|
@ -7,629 +97,540 @@ Version 7.0
|
|||
|
||||
Released 2018-09-25
|
||||
|
||||
- Drop support for Python 2.6 and 3.3. (`#967`_, `#976`_)
|
||||
- Wrap ``click.Choice``'s missing message. (`#202`_, `#1000`_)
|
||||
- Add native ZSH autocompletion support. (`#323`_, `#865`_)
|
||||
- Document that ANSI color info isn't parsed from bytearrays in
|
||||
Python 2. (`#334`_)
|
||||
- Document byte-stripping behavior of ``CliRunner``. (`#334`_,
|
||||
`#1010`_)
|
||||
- Usage errors now hint at the ``--help`` option. (`#393`_, `#557`_)
|
||||
- Implement streaming pager. (`#409`_, `#889`_)
|
||||
- Extract bar formatting to its own method. (`#414`_)
|
||||
- Drop support for Python 2.6 and 3.3. :pr:`967, 976`
|
||||
- Wrap ``click.Choice``'s missing message. :issue:`202`, :pr:`1000`
|
||||
- Add native ZSH autocompletion support. :issue:`323`, :pr:`865`
|
||||
- Document that ANSI color info isn't parsed from bytearrays in Python
|
||||
2. :issue:`334`
|
||||
- Document byte-stripping behavior of ``CliRunner``. :issue:`334`,
|
||||
:pr:`1010`
|
||||
- Usage errors now hint at the ``--help`` option. :issue:`393`,
|
||||
:pr:`557`
|
||||
- Implement streaming pager. :issue:`409`, :pr:`889`
|
||||
- Extract bar formatting to its own method. :pr:`414`
|
||||
- Add ``DateTime`` type for converting input in given date time
|
||||
formats. (`#423`_)
|
||||
formats. :pr:`423`
|
||||
- ``secho``'s first argument can now be ``None``, like in ``echo``.
|
||||
(`#424`_)
|
||||
:pr:`424`
|
||||
- Fixes a ``ZeroDivisionError`` in ``ProgressBar.make_step``, when the
|
||||
arg passed to the first call of ``ProgressBar.update`` is 0.
|
||||
(`#447`_, `#1012`_)
|
||||
- Show progressbar only if total execution time is visible. (`#487`_)
|
||||
- Added the ability to hide commands and options from help. (`#500`_)
|
||||
- Document that options can be ``required=True``. (`#514`_, `#1022`_)
|
||||
:issue:`447`, :pr:`1012`
|
||||
- Show progressbar only if total execution time is visible. :pr:`487`
|
||||
- Added the ability to hide commands and options from help. :pr:`500`
|
||||
- Document that options can be ``required=True``. :issue:`514`,
|
||||
:pr:`1022`
|
||||
- Non-standalone calls to ``Context.exit`` return the exit code,
|
||||
rather than calling ``sys.exit``. (`#533`_, `#667`_, `#1098`_)
|
||||
rather than calling ``sys.exit``. :issue:`667`, :pr:`533, 1098`
|
||||
- ``click.getchar()`` returns Unicode in Python 3 on Windows,
|
||||
consistent with other platforms. (`#537`_, `#821`_, `#822`_,
|
||||
`#1088`_, `#1108`_)
|
||||
- Added ``FloatRange`` type. (`#538`_, `#553`_)
|
||||
consistent with other platforms. :issue:`537, 821, 822, 1088`,
|
||||
:pr:`1108`
|
||||
- Added ``FloatRange`` type. :pr:`538, 553`
|
||||
- Added support for bash completion of ``type=click.Choice`` for
|
||||
``Options`` and ``Arguments``. (`#535`_, `#681`_)
|
||||
``Options`` and ``Arguments``. :issue:`535`, :pr:`681`
|
||||
- Only allow one positional arg for ``Argument`` parameter
|
||||
declaration. (`#568`_, `#574`_, `#1014`_)
|
||||
- Add ``case_sensitive=False`` as an option to Choice. (`#569`_)
|
||||
declaration. :issue:`568, 574`, :pr:`1014`
|
||||
- Add ``case_sensitive=False`` as an option to Choice. :issue:`569`
|
||||
- ``click.getchar()`` correctly raises ``KeyboardInterrupt`` on "^C"
|
||||
and ``EOFError`` on "^D" on Linux. (`#583`_, `#1115`_)
|
||||
and ``EOFError`` on "^D" on Linux. :issue:`583`, :pr:`1115`
|
||||
- Fix encoding issue with ``click.getchar(echo=True)`` on Linux.
|
||||
(`#1115`_)
|
||||
- ``param_hint`` in errors now derived from param itself. (`#598`_,
|
||||
`#704`_, `#709`_)
|
||||
:pr:`1115`
|
||||
- ``param_hint`` in errors now derived from param itself.
|
||||
:issue:`598, 704`, :pr:`709`
|
||||
- Add a test that ensures that when an argument is formatted into a
|
||||
usage error, its metavar is used, not its name. (`#612`_)
|
||||
usage error, its metavar is used, not its name. :pr:`612`
|
||||
- Allow setting ``prog_name`` as extra in ``CliRunner.invoke``.
|
||||
(`#616`_, `#999`_)
|
||||
:issue:`616`, :pr:`999`
|
||||
- Help text taken from docstrings truncates at the ``\f`` form feed
|
||||
character, useful for hiding Sphinx-style parameter documentation.
|
||||
(`#629`_, `#1091`_)
|
||||
- ``launch`` now works properly under Cygwin. (`#650`_)
|
||||
- Update progress after iteration. (`#651`_, `#706`_)
|
||||
:pr:`629, 1091`
|
||||
- ``launch`` now works properly under Cygwin. :pr:`650`
|
||||
- Update progress after iteration. :issue:`651`, :pr:`706`
|
||||
- ``CliRunner.invoke`` now may receive ``args`` as a string
|
||||
representing a Unix shell command. (`#664`_)
|
||||
- Make ``Argument.make_metavar()`` default to type metavar. (`#675`_)
|
||||
- Add documentation for ``ignore_unknown_options``. (`#684`_)
|
||||
representing a Unix shell command. :pr:`664`
|
||||
- Make ``Argument.make_metavar()`` default to type metavar. :pr:`675`
|
||||
- Add documentation for ``ignore_unknown_options``. :pr:`684`
|
||||
- Add bright colors support for ``click.style`` and fix the reset
|
||||
option for parameters ``fg`` and ``bg``. (`#703`_, `#809`_)
|
||||
option for parameters ``fg`` and ``bg``. :issue:`703`, :pr:`809`
|
||||
- Add ``show_envvar`` for showing environment variables in help.
|
||||
(`#710`_)
|
||||
:pr:`710`
|
||||
- Avoid ``BrokenPipeError`` during interpreter shutdown when stdout or
|
||||
stderr is a closed pipe. (`#712`_, `#1106`_)
|
||||
- Document customizing option names. (`#725`_, `#1016`_)
|
||||
stderr is a closed pipe. :issue:`712`, :pr:`1106`
|
||||
- Document customizing option names. :issue:`725`, :pr:`1016`
|
||||
- Disable ``sys._getframes()`` on Python interpreters that don't
|
||||
support it. (`#728`_)
|
||||
support it. :pr:`728`
|
||||
- Fix bug in test runner when calling ``sys.exit`` with ``None``.
|
||||
(`#739`_)
|
||||
- Clarify documentation on command line options. (`#741`_, `#1003`_)
|
||||
- Fix crash on Windows console. (`#744`_)
|
||||
:pr:`739`
|
||||
- Clarify documentation on command line options. :issue:`741`,
|
||||
:pr:`1003`
|
||||
- Fix crash on Windows console. :issue:`744`
|
||||
- Fix bug that caused bash completion to give improper completions on
|
||||
chained commands. (`#754`_, `#774`_)
|
||||
chained commands. :issue:`754`, :pr:`774`
|
||||
- Added support for dynamic bash completion from a user-supplied
|
||||
callback. (`#755`_)
|
||||
- Added support for bash completions containing spaces. (`#773`_)
|
||||
callback. :pr:`755`
|
||||
- Added support for bash completions containing spaces. :pr:`773`
|
||||
- Allow autocompletion function to determine whether or not to return
|
||||
completions that start with the incomplete argument. (`#790`_,
|
||||
`#806`_)
|
||||
completions that start with the incomplete argument. :issue:`790`,
|
||||
:pr:`806`
|
||||
- Fix option naming routine to match documentation and be
|
||||
deterministic. (`#793`_, `#794`_)
|
||||
- Fix path validation bug. (`#795`_, `#1020`_)
|
||||
deterministic. :issue:`793`, :pr:`794`
|
||||
- Fix path validation bug. :issue:`795`, :pr:`1020`
|
||||
- Add test and documentation for ``Option`` naming: functionality.
|
||||
(`#799`_)
|
||||
- Update doc to match arg name for ``path_type``. (`#801`_)
|
||||
- Raw strings added so correct escaping occurs. (`#807`_)
|
||||
- Fix 16k character limit of ``click.echo`` on Windows. (`#816`_,
|
||||
`#819`_)
|
||||
:pr:`799`
|
||||
- Update doc to match arg name for ``path_type``. :pr:`801`
|
||||
- Raw strings added so correct escaping occurs. :pr:`807`
|
||||
- Fix 16k character limit of ``click.echo`` on Windows. :issue:`816`,
|
||||
:pr:`819`
|
||||
- Overcome 64k character limit when writing to binary stream on
|
||||
Windows 7. (`#825`_, `#830`_)
|
||||
- Add bool conversion for "t" and "f". (`#842`_)
|
||||
Windows 7. :issue:`825`, :pr:`830`
|
||||
- Add bool conversion for "t" and "f". :pr:`842`
|
||||
- ``NoSuchOption`` errors take ``ctx`` so that ``--help`` hint gets
|
||||
printed in error output. (`#860`_)
|
||||
printed in error output. :pr:`860`
|
||||
- Fixed the behavior of Click error messages with regards to Unicode
|
||||
on 2.x and 3.x. Message is now always Unicode and the str and
|
||||
Unicode special methods work as you expect on that platform.
|
||||
(`#862`_)
|
||||
- Progress bar now uses stderr by default. (`#863`_)
|
||||
- Add support for auto-completion documentation. (`#866`_, `#869`_)
|
||||
- Allow ``CliRunner`` to separate stdout and stderr. (`#868`_)
|
||||
- Fix variable precedence. (`#873`_, `#874`_)
|
||||
- Fix invalid escape sequences. (`#877`_)
|
||||
- Fix ``ResourceWarning`` that occurs during some tests. (`#878`_)
|
||||
:issue:`862`
|
||||
- Progress bar now uses stderr by default. :pr:`863`
|
||||
- Add support for auto-completion documentation. :issue:`866`,
|
||||
:pr:`869`
|
||||
- Allow ``CliRunner`` to separate stdout and stderr. :pr:`868`
|
||||
- Fix variable precedence. :issue:`873`, :pr:`874`
|
||||
- Fix invalid escape sequences. :pr:`877`
|
||||
- Fix ``ResourceWarning`` that occurs during some tests. :pr:`878`
|
||||
- When detecting a misconfigured locale, don't fail if the ``locale``
|
||||
command fails. (`#880`_)
|
||||
command fails. :pr:`880`
|
||||
- Add ``case_sensitive=False`` as an option to ``Choice`` types.
|
||||
(`#887`_)
|
||||
:pr:`887`
|
||||
- Force stdout/stderr writable. This works around issues with badly
|
||||
patched standard streams like those from Jupyter. (`#918`_)
|
||||
- Fix completion of subcommand options after last argument (`#919`_,
|
||||
`#930`_)
|
||||
patched standard streams like those from Jupyter. :pr:`918`
|
||||
- Fix completion of subcommand options after last argument
|
||||
:issue:`919`, :pr:`930`
|
||||
- ``_AtomicFile`` now uses the ``realpath`` of the original filename
|
||||
so that changing the working directory does not affect it.
|
||||
(`#920`_)
|
||||
- Fix incorrect completions when defaults are present (`#925`_,
|
||||
`#930`_)
|
||||
so that changing the working directory does not affect it. :pr:`920`
|
||||
- Fix incorrect completions when defaults are present :issue:`925`,
|
||||
:pr:`930`
|
||||
- Add copy option attrs so that custom classes can be re-used.
|
||||
(`#926`_, `#994`_)
|
||||
:issue:`926`, :pr:`994`
|
||||
- "x" and "a" file modes now use stdout when file is ``"-"``.
|
||||
(`#929`_)
|
||||
- Fix missing comma in ``__all__`` list. (`#935`_)
|
||||
- Clarify how parameters are named. (`#949`_, `#1009`_)
|
||||
- Stdout is now automatically set to non blocking. (`#954`_)
|
||||
- Do not set options twice. (`#962`_)
|
||||
- Move ``fcntl`` import. (`#965`_)
|
||||
- Fix Google App Engine ``ImportError``. (`#995`_)
|
||||
:pr:`929`
|
||||
- Fix missing comma in ``__all__`` list. :pr:`935`
|
||||
- Clarify how parameters are named. :issue:`949`, :pr:`1009`
|
||||
- Stdout is now automatically set to non blocking. :pr:`954`
|
||||
- Do not set options twice. :pr:`962`
|
||||
- Move ``fcntl`` import. :pr:`965`
|
||||
- Fix Google App Engine ``ImportError``. :pr:`995`
|
||||
- Better handling of help text for dynamic default option values.
|
||||
(`#996`_)
|
||||
:pr:`996`
|
||||
- Fix ``get_winter_size()`` so it correctly returns ``(0,0)``.
|
||||
(`#997`_)
|
||||
- Add test case checking for custom param type. (`#1001`_)
|
||||
- Allow short width to address cmd formatting. (`#1002`_)
|
||||
- Add details about Python version support. (`#1004`_)
|
||||
- Added deprecation flag to commands. (`#1005`_)
|
||||
- Fixed issues where ``fd`` was undefined. (`#1007`_)
|
||||
- Fix formatting for short help. (`#1008`_)
|
||||
:pr:`997`
|
||||
- Add test case checking for custom param type. :pr:`1001`
|
||||
- Allow short width to address cmd formatting. :pr:`1002`
|
||||
- Add details about Python version support. :pr:`1004`
|
||||
- Added deprecation flag to commands. :pr:`1005`
|
||||
- Fixed issues where ``fd`` was undefined. :pr:`1007`
|
||||
- Fix formatting for short help. :pr:`1008`
|
||||
- Document how ``auto_envvar_prefix`` works with command groups.
|
||||
(`#1011`_)
|
||||
- Don't add newlines by default for progress bars. (`#1013`_)
|
||||
- Use Python sorting order for ZSH completions. (`#1047`_, `#1059`_)
|
||||
:pr:`1011`
|
||||
- Don't add newlines by default for progress bars. :pr:`1013`
|
||||
- Use Python sorting order for ZSH completions. :issue:`1047`,
|
||||
:pr:`1059`
|
||||
- Document that parameter names are converted to lowercase by default.
|
||||
(`#1055`_)
|
||||
:pr:`1055`
|
||||
- Subcommands that are named by the function now automatically have
|
||||
the underscore replaced with a dash. If you register a function
|
||||
named ``my_command`` it becomes ``my-command`` in the command line
|
||||
interface.
|
||||
- Hide hidden commands and options from completion. (`#1058`_,
|
||||
`#1061`_)
|
||||
- Hide hidden commands and options from completion. :issue:`1058`,
|
||||
:pr:`1061`
|
||||
- Fix absolute import blocking Click from being vendored into a
|
||||
project on Windows. (`#1068`_, `#1069`_)
|
||||
project on Windows. :issue:`1068`, :pr:`1069`
|
||||
- Fix issue where a lowercase ``auto_envvar_prefix`` would not be
|
||||
converted to uppercase. (`#1105`_)
|
||||
|
||||
.. _#202: https://github.com/pallets/click/issues/202
|
||||
.. _#323: https://github.com/pallets/click/issues/323
|
||||
.. _#334: https://github.com/pallets/click/issues/334
|
||||
.. _#393: https://github.com/pallets/click/issues/393
|
||||
.. _#409: https://github.com/pallets/click/issues/409
|
||||
.. _#414: https://github.com/pallets/click/pull/414
|
||||
.. _#423: https://github.com/pallets/click/pull/423
|
||||
.. _#424: https://github.com/pallets/click/pull/424
|
||||
.. _#447: https://github.com/pallets/click/issues/447
|
||||
.. _#487: https://github.com/pallets/click/pull/487
|
||||
.. _#500: https://github.com/pallets/click/pull/500
|
||||
.. _#514: https://github.com/pallets/click/issues/514
|
||||
.. _#533: https://github.com/pallets/click/pull/533
|
||||
.. _#535: https://github.com/pallets/click/issues/535
|
||||
.. _#537: https://github.com/pallets/click/issues/537
|
||||
.. _#538: https://github.com/pallets/click/pull/538
|
||||
.. _#553: https://github.com/pallets/click/pull/553
|
||||
.. _#557: https://github.com/pallets/click/pull/557
|
||||
.. _#568: https://github.com/pallets/click/issues/568
|
||||
.. _#569: https://github.com/pallets/click/issues/569
|
||||
.. _#574: https://github.com/pallets/click/issues/574
|
||||
.. _#583: https://github.com/pallets/click/issues/583
|
||||
.. _#598: https://github.com/pallets/click/issues/598
|
||||
.. _#612: https://github.com/pallets/click/pull/612
|
||||
.. _#616: https://github.com/pallets/click/issues/616
|
||||
.. _#629: https://github.com/pallets/click/pull/629
|
||||
.. _#650: https://github.com/pallets/click/pull/650
|
||||
.. _#651: https://github.com/pallets/click/issues/651
|
||||
.. _#664: https://github.com/pallets/click/pull/664
|
||||
.. _#667: https://github.com/pallets/click/issues/667
|
||||
.. _#675: https://github.com/pallets/click/pull/675
|
||||
.. _#681: https://github.com/pallets/click/pull/681
|
||||
.. _#684: https://github.com/pallets/click/pull/684
|
||||
.. _#703: https://github.com/pallets/click/issues/703
|
||||
.. _#704: https://github.com/pallets/click/issues/704
|
||||
.. _#706: https://github.com/pallets/click/pull/706
|
||||
.. _#709: https://github.com/pallets/click/pull/709
|
||||
.. _#710: https://github.com/pallets/click/pull/710
|
||||
.. _#712: https://github.com/pallets/click/pull/712
|
||||
.. _#719: https://github.com/pallets/click/issues/719
|
||||
.. _#725: https://github.com/pallets/click/issues/725
|
||||
.. _#728: https://github.com/pallets/click/pull/728
|
||||
.. _#739: https://github.com/pallets/click/pull/739
|
||||
.. _#741: https://github.com/pallets/click/issues/741
|
||||
.. _#744: https://github.com/pallets/click/issues/744
|
||||
.. _#754: https://github.com/pallets/click/issues/754
|
||||
.. _#755: https://github.com/pallets/click/pull/755
|
||||
.. _#773: https://github.com/pallets/click/pull/773
|
||||
.. _#774: https://github.com/pallets/click/pull/774
|
||||
.. _#790: https://github.com/pallets/click/issues/790
|
||||
.. _#793: https://github.com/pallets/click/issues/793
|
||||
.. _#794: https://github.com/pallets/click/pull/794
|
||||
.. _#795: https://github.com/pallets/click/issues/795
|
||||
.. _#799: https://github.com/pallets/click/pull/799
|
||||
.. _#801: https://github.com/pallets/click/pull/801
|
||||
.. _#806: https://github.com/pallets/click/pull/806
|
||||
.. _#807: https://github.com/pallets/click/pull/807
|
||||
.. _#809: https://github.com/pallets/click/pull/809
|
||||
.. _#816: https://github.com/pallets/click/pull/816
|
||||
.. _#819: https://github.com/pallets/click/pull/819
|
||||
.. _#821: https://github.com/pallets/click/issues/821
|
||||
.. _#822: https://github.com/pallets/click/issues/822
|
||||
.. _#825: https://github.com/pallets/click/issues/825
|
||||
.. _#830: https://github.com/pallets/click/pull/830
|
||||
.. _#842: https://github.com/pallets/click/pull/842
|
||||
.. _#860: https://github.com/pallets/click/issues/860
|
||||
.. _#862: https://github.com/pallets/click/issues/862
|
||||
.. _#863: https://github.com/pallets/click/pull/863
|
||||
.. _#865: https://github.com/pallets/click/pull/865
|
||||
.. _#866: https://github.com/pallets/click/issues/866
|
||||
.. _#868: https://github.com/pallets/click/pull/868
|
||||
.. _#869: https://github.com/pallets/click/pull/869
|
||||
.. _#873: https://github.com/pallets/click/issues/873
|
||||
.. _#874: https://github.com/pallets/click/pull/874
|
||||
.. _#877: https://github.com/pallets/click/pull/877
|
||||
.. _#878: https://github.com/pallets/click/pull/878
|
||||
.. _#880: https://github.com/pallets/click/pull/880
|
||||
.. _#883: https://github.com/pallets/click/pull/883
|
||||
.. _#887: https://github.com/pallets/click/pull/887
|
||||
.. _#889: https://github.com/pallets/click/pull/889
|
||||
.. _#918: https://github.com/pallets/click/pull/918
|
||||
.. _#919: https://github.com/pallets/click/issues/919
|
||||
.. _#920: https://github.com/pallets/click/pull/920
|
||||
.. _#925: https://github.com/pallets/click/issues/925
|
||||
.. _#926: https://github.com/pallets/click/issues/926
|
||||
.. _#929: https://github.com/pallets/click/pull/929
|
||||
.. _#930: https://github.com/pallets/click/pull/930
|
||||
.. _#935: https://github.com/pallets/click/pull/935
|
||||
.. _#949: https://github.com/pallets/click/issues/949
|
||||
.. _#954: https://github.com/pallets/click/pull/954
|
||||
.. _#962: https://github.com/pallets/click/pull/962
|
||||
.. _#965: https://github.com/pallets/click/pull/965
|
||||
.. _#967: https://github.com/pallets/click/pull/967
|
||||
.. _#976: https://github.com/pallets/click/pull/976
|
||||
.. _#990: https://github.com/pallets/click/pull/990
|
||||
.. _#991: https://github.com/pallets/click/pull/991
|
||||
.. _#993: https://github.com/pallets/click/pull/993
|
||||
.. _#994: https://github.com/pallets/click/pull/994
|
||||
.. _#995: https://github.com/pallets/click/pull/995
|
||||
.. _#996: https://github.com/pallets/click/pull/996
|
||||
.. _#997: https://github.com/pallets/click/pull/997
|
||||
.. _#999: https://github.com/pallets/click/pull/999
|
||||
.. _#1000: https://github.com/pallets/click/pull/1000
|
||||
.. _#1001: https://github.com/pallets/click/pull/1001
|
||||
.. _#1002: https://github.com/pallets/click/pull/1002
|
||||
.. _#1003: https://github.com/pallets/click/pull/1003
|
||||
.. _#1004: https://github.com/pallets/click/pull/1004
|
||||
.. _#1005: https://github.com/pallets/click/pull/1005
|
||||
.. _#1007: https://github.com/pallets/click/pull/1007
|
||||
.. _#1008: https://github.com/pallets/click/pull/1008
|
||||
.. _#1009: https://github.com/pallets/click/pull/1009
|
||||
.. _#1010: https://github.com/pallets/click/pull/1010
|
||||
.. _#1011: https://github.com/pallets/click/pull/1011
|
||||
.. _#1012: https://github.com/pallets/click/pull/1012
|
||||
.. _#1013: https://github.com/pallets/click/pull/1013
|
||||
.. _#1014: https://github.com/pallets/click/pull/1014
|
||||
.. _#1016: https://github.com/pallets/click/pull/1016
|
||||
.. _#1020: https://github.com/pallets/click/pull/1020
|
||||
.. _#1022: https://github.com/pallets/click/pull/1022
|
||||
.. _#1027: https://github.com/pallets/click/pull/1027
|
||||
.. _#1047: https://github.com/pallets/click/pull/1047
|
||||
.. _#1055: https://github.com/pallets/click/pull/1055
|
||||
.. _#1058: https://github.com/pallets/click/pull/1058
|
||||
.. _#1059: https://github.com/pallets/click/pull/1059
|
||||
.. _#1061: https://github.com/pallets/click/pull/1061
|
||||
.. _#1068: https://github.com/pallets/click/issues/1068
|
||||
.. _#1069: https://github.com/pallets/click/pull/1069
|
||||
.. _#1088: https://github.com/pallets/click/issues/1088
|
||||
.. _#1091: https://github.com/pallets/click/pull/1091
|
||||
.. _#1098: https://github.com/pallets/click/pull/1098
|
||||
.. _#1105: https://github.com/pallets/click/pull/1105
|
||||
.. _#1106: https://github.com/pallets/click/pull/1106
|
||||
.. _#1108: https://github.com/pallets/click/pull/1108
|
||||
.. _#1115: https://github.com/pallets/click/pull/1115
|
||||
converted to uppercase. :pr:`1105`
|
||||
|
||||
|
||||
Version 6.7
|
||||
-----------
|
||||
|
||||
(bugfix release; released on January 6th 2017)
|
||||
Released 2017-01-06
|
||||
|
||||
- Make ``click.progressbar`` work with ``codecs.open`` files.
|
||||
:pr:`637`
|
||||
- Fix bug in bash completion with nested subcommands. :pr:`639`
|
||||
- Fix test runner not saving caller env correctly. :pr:`644`
|
||||
- Fix handling of SIGPIPE. :pr:`62`
|
||||
- Deal with broken Windows environments such as Google App Engine's.
|
||||
:issue:`711`
|
||||
|
||||
- Make ``click.progressbar`` work with ``codecs.open`` files. See #637.
|
||||
- Fix bug in bash completion with nested subcommands. See #639.
|
||||
- Fix test runner not saving caller env correctly. See #644.
|
||||
- Fix handling of SIGPIPE. See #626
|
||||
- Deal with broken Windows environments such as Google App Engine's. See #711.
|
||||
|
||||
Version 6.6
|
||||
-----------
|
||||
|
||||
(bugfix release; released on April 4th 2016)
|
||||
Released 2016-04-04
|
||||
|
||||
- Fix bug in ``click.Path`` where it would crash when passed a ``-``.
|
||||
:issue:`551`
|
||||
|
||||
- Fix bug in ``click.Path`` where it would crash when passed a ``-``. See #551.
|
||||
|
||||
Version 6.4
|
||||
-----------
|
||||
|
||||
(bugfix release; released on March 24th 2016)
|
||||
Released 2016-03-24
|
||||
|
||||
- Fix bug in bash completion where click would discard one or more
|
||||
trailing arguments. :issue:`471`
|
||||
|
||||
- Fix bug in bash completion where click would discard one or more trailing
|
||||
arguments. See #471.
|
||||
|
||||
Version 6.3
|
||||
-----------
|
||||
|
||||
(bugfix release; released on February 22 2016)
|
||||
Released 2016-02-22
|
||||
|
||||
- Fix argument checks for interpreter invoke with ``-m`` and ``-c`` on
|
||||
Windows.
|
||||
- Fixed a bug that cased locale detection to error out on Python 3.
|
||||
|
||||
- Fix argument checks for interpreter invoke with ``-m`` and ``-c``
|
||||
on Windows.
|
||||
- Fixed a bug that cased locale detection to error out on Python 3.
|
||||
|
||||
Version 6.2
|
||||
-----------
|
||||
|
||||
(bugfix release, released on November 27th 2015)
|
||||
Released 2015-11-27
|
||||
|
||||
- Correct fix for hidden progress bars.
|
||||
|
||||
- Correct fix for hidden progress bars.
|
||||
|
||||
Version 6.1
|
||||
-----------
|
||||
|
||||
(bugfix release, released on November 27th 2015)
|
||||
Released 2015-11-27
|
||||
|
||||
- Resolved an issue with invisible progress bars no longer rendering.
|
||||
- Disable chain commands with subcommands as they were inherently
|
||||
broken.
|
||||
- Fix ``MissingParameter`` not working without parameters passed.
|
||||
|
||||
- Resolved an issue with invisible progress bars no longer rendering.
|
||||
- Disable chain commands with subcommands as they were inherently broken.
|
||||
- Fix ``MissingParameter`` not working without parameters passed.
|
||||
|
||||
Version 6.0
|
||||
-----------
|
||||
|
||||
(codename "pow pow", released on November 24th 2015)
|
||||
Released 2015-11-24, codename "pow pow"
|
||||
|
||||
- Optimized the progressbar rendering to not render when it did not
|
||||
actually change.
|
||||
- Explicitly disallow ``nargs=-1`` with a set default.
|
||||
- The context is now closed before it's popped from the stack.
|
||||
- Added support for short aliases for the false flag on toggles.
|
||||
- Click will now attempt to aid you with debugging locale errors
|
||||
better by listing with the help of the OS what locales are
|
||||
available.
|
||||
- Click used to return byte strings on Python 2 in some unit-testing
|
||||
situations. This has been fixed to correctly return unicode strings
|
||||
now.
|
||||
- For Windows users on Python 2, Click will now handle Unicode more
|
||||
correctly handle Unicode coming in from the system. This also has
|
||||
the disappointing side effect that filenames will now be always
|
||||
unicode by default in the ``Path`` type which means that this can
|
||||
introduce small bugs for code not aware of this.
|
||||
- Added a ``type`` parameter to ``Path`` to force a specific string
|
||||
type on the value.
|
||||
- For users running Python on Windows the ``echo`` and ``prompt``
|
||||
functions now work with full unicode functionality in the Python
|
||||
windows console by emulating an output stream. This also applies to
|
||||
getting the virtual output and input streams via
|
||||
``click.get_text_stream(...)``.
|
||||
- Unittests now always force a certain virtual terminal width.
|
||||
- Added support for allowing dashes to indicate standard streams to
|
||||
the ``Path`` type.
|
||||
- Multi commands in chain mode no longer propagate arguments left over
|
||||
from parsing to the callbacks. It's also now disallowed through an
|
||||
exception when optional arguments are attached to multi commands if
|
||||
chain mode is enabled.
|
||||
- Relaxed restriction that disallowed chained commands to have other
|
||||
chained commands as child commands.
|
||||
- Arguments with positive nargs can now have defaults implemented.
|
||||
Previously this configuration would often result in slightly
|
||||
unexpected values be returned.
|
||||
|
||||
- Optimized the progressbar rendering to not render when it did not
|
||||
actually change.
|
||||
- Explicitly disallow ``nargs=-1`` with a set default.
|
||||
- The context is now closed before it's popped from the stack.
|
||||
- Added support for short aliases for the false flag on toggles.
|
||||
- Click will now attempt to aid you with debugging locale errors
|
||||
better by listing with the help of the OS what locales are
|
||||
available.
|
||||
- Click used to return byte strings on Python 2 in some unit-testing
|
||||
situations. This has been fixed to correctly return unicode strings
|
||||
now.
|
||||
- For Windows users on Python 2, Click will now handle Unicode more
|
||||
correctly handle Unicode coming in from the system. This also has
|
||||
the disappointing side effect that filenames will now be always
|
||||
unicode by default in the ``Path`` type which means that this can
|
||||
introduce small bugs for code not aware of this.
|
||||
- Added a ``type`` parameter to ``Path`` to force a specific string type
|
||||
on the value.
|
||||
- For users running Python on Windows the ``echo`` and ``prompt`` functions
|
||||
now work with full unicode functionality in the Python windows console
|
||||
by emulating an output stream. This also applies to getting the
|
||||
virtual output and input streams via ``click.get_text_stream(...)``.
|
||||
- Unittests now always force a certain virtual terminal width.
|
||||
- Added support for allowing dashes to indicate standard streams to the
|
||||
``Path`` type.
|
||||
- Multi commands in chain mode no longer propagate arguments left over
|
||||
from parsing to the callbacks. It's also now disallowed through an
|
||||
exception when optional arguments are attached to multi commands if chain
|
||||
mode is enabled.
|
||||
- Relaxed restriction that disallowed chained commands to have other
|
||||
chained commands as child commands.
|
||||
- Arguments with positive nargs can now have defaults implemented.
|
||||
Previously this configuration would often result in slightly unexpected
|
||||
values be returned.
|
||||
|
||||
Version 5.1
|
||||
-----------
|
||||
|
||||
(bugfix release, released on 17th August 2015)
|
||||
Released 2015-08-17
|
||||
|
||||
- Fix a bug in ``pass_obj`` that would accidentally pass the context
|
||||
too.
|
||||
|
||||
- Fix a bug in ``pass_obj`` that would accidentally pass the context too.
|
||||
|
||||
Version 5.0
|
||||
-----------
|
||||
|
||||
(codename "tok tok", released on 16th August 2015)
|
||||
Released 2015-08-16, codename "tok tok"
|
||||
|
||||
- Removed various deprecated functionality.
|
||||
- Atomic files now only accept the ``w`` mode.
|
||||
- Change the usage part of help output for very long commands to wrap
|
||||
their arguments onto the next line, indented by 4 spaces.
|
||||
- Fix a bug where return code and error messages were incorrect when
|
||||
using ``CliRunner``.
|
||||
- Added ``get_current_context``.
|
||||
- Added a ``meta`` dictionary to the context which is shared across
|
||||
the linked list of contexts to allow click utilities to place state
|
||||
there.
|
||||
- Introduced ``Context.scope``.
|
||||
- The ``echo`` function is now threadsafe: It calls the ``write``
|
||||
method of the underlying object only once.
|
||||
- ``prompt(hide_input=True)`` now prints a newline on ``^C``.
|
||||
- Click will now warn if users are using ``unicode_literals``.
|
||||
- Click will now ignore the ``PAGER`` environment variable if it is
|
||||
empty or contains only whitespace.
|
||||
- The ``click-contrib`` GitHub organization was created.
|
||||
|
||||
- Removed various deprecated functionality.
|
||||
- Atomic files now only accept the ``w`` mode.
|
||||
- Change the usage part of help output for very long commands to wrap
|
||||
their arguments onto the next line, indented by 4 spaces.
|
||||
- Fix a bug where return code and error messages were incorrect when
|
||||
using ``CliRunner``.
|
||||
- added ``get_current_context``.
|
||||
- added a ``meta`` dictionary to the context which is shared across the
|
||||
linked list of contexts to allow click utilities to place state there.
|
||||
- introduced ``Context.scope``.
|
||||
- The ``echo`` function is now threadsafe: It calls the ``write`` method of the
|
||||
underlying object only once.
|
||||
- ``prompt(hide_input=True)`` now prints a newline on ``^C``.
|
||||
- Click will now warn if users are using ``unicode_literals``.
|
||||
- Click will now ignore the ``PAGER`` environment variable if it is empty or
|
||||
contains only whitespace.
|
||||
- The ``click-contrib`` GitHub organization was created.
|
||||
|
||||
Version 4.1
|
||||
-----------
|
||||
|
||||
(bugfix release, released on July 14th 2015)
|
||||
Released 2015-07-14
|
||||
|
||||
- Fix a bug where error messages would include a trailing ``None``
|
||||
string.
|
||||
- Fix a bug where Click would crash on docstrings with trailing
|
||||
newlines.
|
||||
- Support streams with encoding set to ``None`` on Python 3 by barfing
|
||||
with a better error.
|
||||
- Handle ^C in less-pager properly.
|
||||
- Handle return value of ``None`` from ``sys.getfilesystemencoding``
|
||||
- Fix crash when writing to unicode files with ``click.echo``.
|
||||
- Fix type inference with multiple options.
|
||||
|
||||
- Fix a bug where error messages would include a trailing ``None`` string.
|
||||
- Fix a bug where Click would crash on docstrings with trailing newlines.
|
||||
- Support streams with encoding set to ``None`` on Python 3 by barfing with
|
||||
a better error.
|
||||
- Handle ^C in less-pager properly.
|
||||
- Handle return value of ``None`` from ``sys.getfilesystemencoding``
|
||||
- Fix crash when writing to unicode files with ``click.echo``.
|
||||
- Fix type inference with multiple options.
|
||||
|
||||
Version 4.0
|
||||
-----------
|
||||
|
||||
(codename "zoom zoom", released on March 31st 2015)
|
||||
Released 2015-03-31, codename "zoom zoom"
|
||||
|
||||
- Added ``color`` parameters to lots of interfaces that directly or
|
||||
indirectly call into echoing. This previously was always
|
||||
autodetection (with the exception of the ``echo_via_pager``
|
||||
function). Now you can forcefully enable or disable it, overriding
|
||||
the auto detection of Click.
|
||||
- Added an ``UNPROCESSED`` type which does not perform any type
|
||||
changes which simplifies text handling on 2.x / 3.x in some special
|
||||
advanced usecases.
|
||||
- Added ``NoSuchOption`` and ``BadOptionUsage`` exceptions for more
|
||||
generic handling of errors.
|
||||
- Added support for handling of unprocessed options which can be
|
||||
useful in situations where arguments are forwarded to underlying
|
||||
tools.
|
||||
- Added ``max_content_width`` parameter to the context which can be
|
||||
used to change the maximum width of help output. By default Click
|
||||
will not format content for more than 80 characters width.
|
||||
- Added support for writing prompts to stderr.
|
||||
- Fix a bug when showing the default for multiple arguments.
|
||||
- Added support for custom subclasses to ``option`` and ``argument``.
|
||||
- Fix bug in ``clear()`` on Windows when colorama is installed.
|
||||
- Reject ``nargs=-1`` for options properly. Options cannot be
|
||||
variadic.
|
||||
- Fixed an issue with bash completion not working properly for
|
||||
commands with non ASCII characters or dashes.
|
||||
- Added a way to manually update the progressbar.
|
||||
- Changed the formatting of missing arguments. Previously the internal
|
||||
argument name was shown in error messages, now the metavar is shown
|
||||
if passed. In case an automated metavar is selected, it's stripped
|
||||
of extra formatting first.
|
||||
|
||||
- Added ``color`` parameters to lots of interfaces that directly or indirectly
|
||||
call into echoing. This previously was always autodetection (with the
|
||||
exception of the ``echo_via_pager`` function). Now you can forcefully
|
||||
enable or disable it, overriding the auto detection of Click.
|
||||
- Added an ``UNPROCESSED`` type which does not perform any type changes which
|
||||
simplifies text handling on 2.x / 3.x in some special advanced usecases.
|
||||
- Added ``NoSuchOption`` and ``BadOptionUsage`` exceptions for more generic
|
||||
handling of errors.
|
||||
- Added support for handling of unprocessed options which can be useful in
|
||||
situations where arguments are forwarded to underlying tools.
|
||||
- Added ``max_content_width`` parameter to the context which can be used to
|
||||
change the maximum width of help output. By default Click will not format
|
||||
content for more than 80 characters width.
|
||||
- Added support for writing prompts to stderr.
|
||||
- Fix a bug when showing the default for multiple arguments.
|
||||
- Added support for custom subclasses to ``option`` and ``argument``.
|
||||
- Fix bug in ``clear()`` on Windows when colorama is installed.
|
||||
- Reject ``nargs=-1`` for options properly. Options cannot be variadic.
|
||||
- Fixed an issue with bash completion not working properly for commands with
|
||||
non ASCII characters or dashes.
|
||||
- Added a way to manually update the progressbar.
|
||||
- Changed the formatting of missing arguments. Previously the internal
|
||||
argument name was shown in error messages, now the metavar is shown if
|
||||
passed. In case an automated metavar is selected, it's stripped of
|
||||
extra formatting first.
|
||||
|
||||
Version 3.3
|
||||
-----------
|
||||
|
||||
(bugfix release, released on September 8th 2014)
|
||||
Released 2014-09-08
|
||||
|
||||
- Fixed an issue with error reporting on Python 3 for invalid
|
||||
forwarding of commands.
|
||||
|
||||
- Fixed an issue with error reporting on Python 3 for invalid forwarding
|
||||
of commands.
|
||||
|
||||
Version 3.2
|
||||
-----------
|
||||
|
||||
(bugfix release, released on August 22nd 2014)
|
||||
Released 2014-08-22
|
||||
|
||||
- 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.
|
||||
|
||||
- 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)
|
||||
Released 2014-08-13
|
||||
|
||||
- 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.
|
||||
|
||||
- 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)
|
||||
Released 2014-08-12, codename "clonk clonk"
|
||||
|
||||
- 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``.
|
||||
|
||||
- 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)
|
||||
Released 2014-08-11
|
||||
|
||||
- Fixed an issue where the wrapped streams on Python 3 would be
|
||||
reporting incorrect values for seekable.
|
||||
|
||||
- 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)
|
||||
Released 2014-07-28
|
||||
|
||||
- Fixed a bug with text wrapping on Python 3.
|
||||
|
||||
- Fixed a bug with text wrapping on Python 3.
|
||||
|
||||
Version 2.4
|
||||
-----------
|
||||
|
||||
(bugfix release, released on July 4th 2014)
|
||||
Released 2014-07-04
|
||||
|
||||
- Corrected a bug in the change of the help option in 2.3.
|
||||
|
||||
- Corrected a bug in the change of the help option in 2.3.
|
||||
|
||||
Version 2.3
|
||||
-----------
|
||||
|
||||
(bugfix release, released on July 3rd 2014)
|
||||
Released 2014-07-03
|
||||
|
||||
- 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.
|
||||
|
||||
- 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)
|
||||
Released 2014-06-26
|
||||
|
||||
- Fixed tty detection on PyPy.
|
||||
- Fixed an issue that progress bars were not rendered when the context
|
||||
manager was entered.
|
||||
|
||||
- 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)
|
||||
Released 2014-06-14
|
||||
|
||||
- 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.
|
||||
|
||||
- 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)
|
||||
Released 2014-06-06, codename "tap tap tap"
|
||||
|
||||
- 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.
|
||||
|
||||
- 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)
|
||||
Released 2014-05-23
|
||||
|
||||
- Fixed a bug that caused text files in Python 2 to not accept native
|
||||
strings.
|
||||
|
||||
- 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)
|
||||
Released 2014-05-21
|
||||
|
||||
- Initial release.
|
||||
- Initial release.
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
==========================
|
||||
How to contribute to Click
|
||||
==========================
|
||||
|
||||
Thanks for considering contributing to Click.
|
||||
|
||||
Support questions
|
||||
=================
|
||||
|
||||
Please, don't use the issue tracker for this. Check whether the
|
||||
``#pocoo`` IRC channel on Freenode can help with your issue. If your problem
|
||||
is not strictly Click-specific, ``#python`` on Freenode is generally more
|
||||
active. `StackOverflow <https://stackoverflow.com/>`_ is also worth
|
||||
considering.
|
||||
|
||||
Reporting issues
|
||||
================
|
||||
|
||||
- Under which versions of Python does this happen? This is even more important
|
||||
if your issue is encoding related.
|
||||
|
||||
- Under which versions of Click does this happen? Check if this issue is fixed
|
||||
in the repository.
|
||||
|
||||
Submitting patches
|
||||
==================
|
||||
|
||||
- Include tests if your patch is supposed to solve a bug, and explain clearly
|
||||
under which circumstances the bug happens. Make sure the test fails without
|
||||
your patch.
|
||||
|
||||
- Try to follow `PEP8 <http://legacy.python.org/dev/peps/pep-0008/>`_, but you
|
||||
may ignore the line-length-limit if following it would make the code uglier.
|
||||
|
||||
- For features: Consider whether your feature would be a better fit for an
|
||||
`external package <https://click.palletsprojects.com/en/7.x/contrib/>`_
|
||||
|
||||
- For docs and bug fixes: Submit against the latest maintenance branch instead of master!
|
||||
|
||||
Running the testsuite
|
||||
---------------------
|
||||
|
||||
You probably want to set up a `virtualenv
|
||||
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
||||
|
||||
The minimal requirement for running the testsuite is ``py.test``. You can
|
||||
install it with::
|
||||
|
||||
pip install pytest
|
||||
|
||||
Then you can run the testsuite with::
|
||||
|
||||
py.test
|
||||
|
||||
For a more isolated test environment, you can also install ``tox`` instead of
|
||||
``pytest``. You can install it with::
|
||||
|
||||
pip install tox
|
||||
|
||||
The ``tox`` command will then run all tests against multiple combinations of
|
||||
Python versions and dependency versions.
|
47
LICENSE.rst
47
LICENSE.rst
|
@ -1,39 +1,28 @@
|
|||
Copyright © 2014 by the Pallets team.
|
||||
Copyright 2014 Pallets
|
||||
|
||||
Some rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistribution and use in source and binary forms of the software as
|
||||
well as documentation, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
1. 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
|
||||
2. 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.
|
||||
|
||||
- Neither the name of the copyright holder nor the names of its
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE AND DOCUMENTATION 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 HOLDER 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 AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
----
|
||||
|
||||
Click uses parts of optparse written by Gregory P. Ward and maintained
|
||||
by the Python Software Foundation. This is limited to code in parser.py.
|
||||
|
||||
Copyright © 2001-2006 Gregory P. Ward. All rights reserved.
|
||||
Copyright © 2002-2006 Python Software Foundation. All rights reserved.
|
||||
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
|
||||
HOLDER 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.
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
include CHANGES.rst
|
||||
include CONTRIBUTING.rst
|
||||
include LICENSE.rst
|
||||
include README.rst
|
||||
include tox.ini
|
||||
graft artwork
|
||||
graft docs
|
||||
prune docs/_build
|
||||
graft examples
|
||||
graft tests
|
||||
global-exclude *.py[co] .DS_Store
|
||||
global-exclude *.pyc
|
||||
|
|
53
PKG-INFO
53
PKG-INFO
|
@ -1,13 +1,11 @@
|
|||
Metadata-Version: 1.2
|
||||
Name: Click
|
||||
Version: 7.0
|
||||
Name: click
|
||||
Version: 7.1.2
|
||||
Summary: Composable command line interface toolkit
|
||||
Home-page: https://palletsprojects.com/p/click/
|
||||
Author: Armin Ronacher
|
||||
Author-email: armin.ronacher@active-4.com
|
||||
Maintainer: Pallets Team
|
||||
Maintainer: Pallets
|
||||
Maintainer-email: contact@palletsprojects.com
|
||||
License: BSD
|
||||
License: BSD-3-Clause
|
||||
Project-URL: Documentation, https://click.palletsprojects.com/
|
||||
Project-URL: Code, https://github.com/pallets/click
|
||||
Project-URL: Issue tracker, https://github.com/pallets/click/issues
|
||||
|
@ -37,9 +35,7 @@ Description: \$ click\_
|
|||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install click
|
||||
|
||||
Click supports Python 3.4 and newer, Python 2.7, and PyPy.
|
||||
$ pip install -U click
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
||||
|
||||
|
@ -47,26 +43,21 @@ Description: \$ click\_
|
|||
A Simple Example
|
||||
----------------
|
||||
|
||||
What does it look like? Here is an example of a simple Click program:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--count", default=1, help="Number of greetings.")
|
||||
@click.option("--name", prompt="Your name",
|
||||
help="The person to greet.")
|
||||
@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 _ in range(count):
|
||||
click.echo("Hello, %s!" % name)
|
||||
|
||||
click.echo(f"Hello, {name}!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
|
||||
And what it looks like when run:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python hello.py --count=3
|
||||
|
@ -90,18 +81,13 @@ Description: \$ click\_
|
|||
Links
|
||||
-----
|
||||
|
||||
* Website: https://palletsprojects.com/p/click/
|
||||
* Documentation: https://click.palletsprojects.com/
|
||||
* License: `BSD <https://github.com/pallets/click/blob/master/LICENSE.rst>`_
|
||||
* Releases: https://pypi.org/project/click/
|
||||
* Code: https://github.com/pallets/click
|
||||
* Issue tracker: https://github.com/pallets/click/issues
|
||||
* Test status:
|
||||
|
||||
* Linux, Mac: https://travis-ci.org/pallets/click
|
||||
* Windows: https://ci.appveyor.com/project/pallets/click
|
||||
|
||||
* Test coverage: https://codecov.io/gh/pallets/click
|
||||
- Website: https://palletsprojects.com/p/click/
|
||||
- Documentation: https://click.palletsprojects.com/
|
||||
- Releases: https://pypi.org/project/click/
|
||||
- Code: https://github.com/pallets/click
|
||||
- Issue tracker: https://github.com/pallets/click/issues
|
||||
- Test status: https://dev.azure.com/pallets/click/_build
|
||||
- Official chat: https://discord.gg/t6rrQZH
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
|
@ -110,10 +96,5 @@ Classifier: License :: OSI Approved :: BSD License
|
|||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
|
||||
|
|
36
README.rst
36
README.rst
|
@ -24,9 +24,7 @@ Install and update using `pip`_:
|
|||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install click
|
||||
|
||||
Click supports Python 3.4 and newer, Python 2.7, and PyPy.
|
||||
$ pip install -U click
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
||||
|
||||
|
@ -34,26 +32,21 @@ Click supports Python 3.4 and newer, Python 2.7, and PyPy.
|
|||
A Simple Example
|
||||
----------------
|
||||
|
||||
What does it look like? Here is an example of a simple Click program:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--count", default=1, help="Number of greetings.")
|
||||
@click.option("--name", prompt="Your name",
|
||||
help="The person to greet.")
|
||||
@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 _ in range(count):
|
||||
click.echo("Hello, %s!" % name)
|
||||
|
||||
click.echo(f"Hello, {name}!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
|
||||
And what it looks like when run:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python hello.py --count=3
|
||||
|
@ -77,15 +70,10 @@ donate today`_.
|
|||
Links
|
||||
-----
|
||||
|
||||
* Website: https://palletsprojects.com/p/click/
|
||||
* Documentation: https://click.palletsprojects.com/
|
||||
* License: `BSD <https://github.com/pallets/click/blob/master/LICENSE.rst>`_
|
||||
* Releases: https://pypi.org/project/click/
|
||||
* Code: https://github.com/pallets/click
|
||||
* Issue tracker: https://github.com/pallets/click/issues
|
||||
* Test status:
|
||||
|
||||
* Linux, Mac: https://travis-ci.org/pallets/click
|
||||
* Windows: https://ci.appveyor.com/project/pallets/click
|
||||
|
||||
* Test coverage: https://codecov.io/gh/pallets/click
|
||||
- Website: https://palletsprojects.com/p/click/
|
||||
- Documentation: https://click.palletsprojects.com/
|
||||
- Releases: https://pypi.org/project/click/
|
||||
- Code: https://github.com/pallets/click
|
||||
- Issue tracker: https://github.com/pallets/click/issues
|
||||
- Test status: https://dev.azure.com/pallets/click/_build
|
||||
- Official chat: https://discord.gg/t6rrQZH
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
click
|
||||
~~~~~
|
||||
|
||||
Click is a simple Python module inspired by the stdlib 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.
|
||||
|
||||
:copyright: © 2014 by the Pallets team.
|
||||
:license: BSD, see LICENSE.rst for more details.
|
||||
"""
|
||||
|
||||
# Core classes
|
||||
from .core import Context, BaseCommand, Command, MultiCommand, Group, \
|
||||
CommandCollection, Parameter, Option, Argument
|
||||
|
||||
# Globals
|
||||
from .globals import get_current_context
|
||||
|
||||
# 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, Tuple, \
|
||||
DateTime, STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange
|
||||
|
||||
# Utilities
|
||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
||||
format_filename, get_app_dir, get_os_args
|
||||
|
||||
# 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, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
|
||||
MissingParameter
|
||||
|
||||
# Formatting
|
||||
from .formatting import HelpFormatter, wrap_text
|
||||
|
||||
# Parsing
|
||||
from .parser import OptionParser
|
||||
|
||||
|
||||
__all__ = [
|
||||
# Core classes
|
||||
'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
|
||||
'CommandCollection', 'Parameter', 'Option', 'Argument',
|
||||
|
||||
# Globals
|
||||
'get_current_context',
|
||||
|
||||
# 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', 'Tuple',
|
||||
'DateTime', 'STRING', 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
|
||||
'FloatRange',
|
||||
|
||||
# Utilities
|
||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||
'format_filename', 'get_app_dir', 'get_os_args',
|
||||
|
||||
# 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', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
|
||||
'MissingParameter',
|
||||
|
||||
# Formatting
|
||||
'HelpFormatter', 'wrap_text',
|
||||
|
||||
# Parsing
|
||||
'OptionParser',
|
||||
]
|
||||
|
||||
|
||||
# Controls if click should emit the warning about the use of unicode
|
||||
# literals.
|
||||
disable_unicode_literals_warning = False
|
||||
|
||||
|
||||
__version__ = '7.0'
|
|
@ -1,125 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from ._compat import 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.
|
||||
click = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||
|
||||
|
||||
def _find_unicode_literals_frame():
|
||||
import __future__
|
||||
if not hasattr(sys, '_getframe'): # not all Python implementations have it
|
||||
return 0
|
||||
frm = sys._getframe(1)
|
||||
idx = 1
|
||||
while frm is not None:
|
||||
if frm.f_globals.get('__name__', '').startswith('click.'):
|
||||
frm = frm.f_back
|
||||
idx += 1
|
||||
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
||||
return idx
|
||||
else:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
def _check_for_unicode_literals():
|
||||
if not __debug__:
|
||||
return
|
||||
if not PY2 or click.disable_unicode_literals_warning:
|
||||
return
|
||||
bad_frame = _find_unicode_literals_frame()
|
||||
if bad_frame <= 0:
|
||||
return
|
||||
from warnings import warn
|
||||
warn(Warning('Click detected the use of the unicode_literals '
|
||||
'__future__ import. This is heavily discouraged '
|
||||
'because it can introduce subtle bugs in your '
|
||||
'code. You should instead use explicit u"" literals '
|
||||
'for your unicode strings. For more information see '
|
||||
'https://click.palletsprojects.com/python3/'),
|
||||
stacklevel=bad_frame)
|
||||
|
||||
|
||||
def _verify_python3_env():
|
||||
"""Ensures that the environment is good for unicode on Python 3."""
|
||||
if PY2:
|
||||
return
|
||||
try:
|
||||
import locale
|
||||
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
||||
except Exception:
|
||||
fs_enc = 'ascii'
|
||||
if fs_enc != 'ascii':
|
||||
return
|
||||
|
||||
extra = ''
|
||||
if os.name == 'posix':
|
||||
import subprocess
|
||||
try:
|
||||
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()[0]
|
||||
except OSError:
|
||||
rv = b''
|
||||
good_locales = set()
|
||||
has_c_utf8 = False
|
||||
|
||||
# Make sure we're operating on text here.
|
||||
if isinstance(rv, bytes):
|
||||
rv = rv.decode('ascii', 'replace')
|
||||
|
||||
for line in rv.splitlines():
|
||||
locale = line.strip()
|
||||
if locale.lower().endswith(('.utf-8', '.utf8')):
|
||||
good_locales.add(locale)
|
||||
if locale.lower() in ('c.utf8', 'c.utf-8'):
|
||||
has_c_utf8 = True
|
||||
|
||||
extra += '\n\n'
|
||||
if not good_locales:
|
||||
extra += (
|
||||
'Additional information: on this system no suitable UTF-8\n'
|
||||
'locales were discovered. This most likely requires resolving\n'
|
||||
'by reconfiguring the locale system.'
|
||||
)
|
||||
elif has_c_utf8:
|
||||
extra += (
|
||||
'This system supports the C.UTF-8 locale which is recommended.\n'
|
||||
'You might be able to resolve your issue by exporting the\n'
|
||||
'following environment variables:\n\n'
|
||||
' export LC_ALL=C.UTF-8\n'
|
||||
' export LANG=C.UTF-8'
|
||||
)
|
||||
else:
|
||||
extra += (
|
||||
'This system lists a couple of UTF-8 supporting locales that\n'
|
||||
'you can pick from. The following suitable locales were\n'
|
||||
'discovered: %s'
|
||||
) % ', '.join(sorted(good_locales))
|
||||
|
||||
bad_locale = None
|
||||
for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'):
|
||||
if locale and locale.lower().endswith(('.utf-8', '.utf8')):
|
||||
bad_locale = locale
|
||||
if locale is not None:
|
||||
break
|
||||
if bad_locale is not None:
|
||||
extra += (
|
||||
'\n\nClick discovered that you exported a UTF-8 locale\n'
|
||||
'but the locale system could not pick up from it because\n'
|
||||
'it does not exist. The exported locale is "%s" but it\n'
|
||||
'is not supported'
|
||||
) % bad_locale
|
||||
|
||||
raise RuntimeError(
|
||||
'Click will abort further execution because Python 3 was'
|
||||
' configured to use ASCII as encoding for the environment.'
|
||||
' Consult https://click.palletsprojects.com/en/7.x/python3/ for'
|
||||
' mitigation steps.' + extra
|
||||
)
|
|
@ -12,9 +12,11 @@ Click. This page should give some insight into what can be accomplished.
|
|||
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.
|
||||
Many tools support aliases for commands (see `Command alias example
|
||||
<https://github.com/pallets/click/tree/master/examples/aliases>`_).
|
||||
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
|
||||
|
|
|
@ -8,8 +8,8 @@ Arguments
|
|||
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.
|
||||
you and wants you to :ref:`document them manually <documenting-arguments>`
|
||||
in order to avoid ugly help pages.
|
||||
|
||||
Basic Arguments
|
||||
---------------
|
||||
|
@ -25,6 +25,7 @@ Example:
|
|||
@click.command()
|
||||
@click.argument('filename')
|
||||
def touch(filename):
|
||||
"""Print FILENAME."""
|
||||
click.echo(filename)
|
||||
|
||||
And what it looks like:
|
||||
|
@ -52,6 +53,7 @@ Example:
|
|||
@click.argument('src', nargs=-1)
|
||||
@click.argument('dst', nargs=1)
|
||||
def copy(src, dst):
|
||||
"""Move file SRC to DST."""
|
||||
for fn in src:
|
||||
click.echo('move %s to folder %s' % (fn, dst))
|
||||
|
||||
|
@ -101,6 +103,7 @@ Example:
|
|||
@click.argument('input', type=click.File('rb'))
|
||||
@click.argument('output', type=click.File('wb'))
|
||||
def inout(input, output):
|
||||
"""Copy contents of INPUT to OUTPUT."""
|
||||
while True:
|
||||
chunk = input.read(1024)
|
||||
if not chunk:
|
||||
|
@ -136,9 +139,10 @@ Example:
|
|||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('f', type=click.Path(exists=True))
|
||||
def touch(f):
|
||||
click.echo(click.format_filename(f))
|
||||
@click.argument('filename', type=click.Path(exists=True))
|
||||
def touch(filename):
|
||||
"""Print FILENAME if the file exists."""
|
||||
click.echo(click.format_filename(filename))
|
||||
|
||||
And what it does:
|
||||
|
||||
|
@ -199,6 +203,7 @@ Example usage:
|
|||
@click.command()
|
||||
@click.argument('src', envvar='SRC', type=click.File('r'))
|
||||
def echo(src):
|
||||
"""Print value of SRC environment variable."""
|
||||
click.echo(src.read())
|
||||
|
||||
And from the command line:
|
||||
|
@ -235,6 +240,7 @@ Example usage:
|
|||
@click.command()
|
||||
@click.argument('files', nargs=-1, type=click.Path())
|
||||
def touch(files):
|
||||
"""Print all FILES file names."""
|
||||
for filename in files:
|
||||
click.echo(filename)
|
||||
|
||||
|
@ -252,6 +258,7 @@ True to avoid checking unknown options:
|
|||
@click.command(context_settings={"ignore_unknown_options": True})
|
||||
@click.argument('files', nargs=-1, type=click.Path())
|
||||
def touch(files):
|
||||
"""Print all FILES file names."""
|
||||
for filename in files:
|
||||
click.echo(filename)
|
||||
|
||||
|
@ -260,4 +267,3 @@ And from the command line:
|
|||
.. click:run::
|
||||
|
||||
invoke(touch, ['-foo.txt', 'bar.txt'])
|
||||
|
||||
|
|
|
@ -1,47 +1,46 @@
|
|||
Bash Complete
|
||||
=============
|
||||
Shell Completion
|
||||
================
|
||||
|
||||
.. 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.
|
||||
Click can provide tab completion for commands, options, and choice
|
||||
values. Bash, Zsh, and Fish are supported
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
Completion is only available if a script is installed and invoked
|
||||
through an entry point, not through the ``python`` command. See
|
||||
:ref:`setuptools-integration`.
|
||||
|
||||
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`. Click currently
|
||||
only supports completion for Bash and Zsh.
|
||||
|
||||
What it Completes
|
||||
-----------------
|
||||
|
||||
Generally, the Bash completion support will complete subcommands, options
|
||||
and any option or argument values where the type is click.Choice.
|
||||
Subcommands and choices are always listed whereas options only if at
|
||||
least a dash has been provided. Example::
|
||||
Generally, the shell completion support will complete commands,
|
||||
options, and any option or argument values where the type is
|
||||
:class:`click.Choice`. Options are only listed if at least a dash has
|
||||
been entered.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ repo <TAB><TAB>
|
||||
clone commit copy delete setuser
|
||||
$ repo clone -<TAB><TAB>
|
||||
--deep --help --rev --shallow -r
|
||||
|
||||
Additionally, custom suggestions can be provided for arguments and options with
|
||||
the ``autocompletion`` parameter. ``autocompletion`` should a callback function
|
||||
that returns a list of strings. This is useful when the suggestions need to be
|
||||
dynamically generated at bash completion time. The callback function will be
|
||||
passed 3 keyword arguments:
|
||||
Custom completions can be provided for argument and option values by
|
||||
providing an ``autocompletion`` function that returns a list of strings.
|
||||
This is useful when the suggestions need to be dynamically generated
|
||||
completion time. The callback function will be passed 3 keyword
|
||||
arguments:
|
||||
|
||||
- ``ctx`` - The current click context.
|
||||
- ``args`` - The list of arguments passed in.
|
||||
- ``incomplete`` - The partial word that is being completed, as a string. May
|
||||
be an empty string ``''`` if no characters have been entered yet.
|
||||
- ``ctx`` - The current command context.
|
||||
- ``args`` - The list of arguments passed in.
|
||||
- ``incomplete`` - The partial word that is being completed. May
|
||||
be an empty string if no characters have been entered yet.
|
||||
|
||||
Here is an example of using a callback function to generate dynamic suggestions:
|
||||
Here is an example of using a callback function to generate dynamic
|
||||
suggestions:
|
||||
|
||||
.. click:example::
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
|
@ -55,25 +54,26 @@ Here is an example of using a callback function to generate dynamic suggestions:
|
|||
click.echo('Value: %s' % os.environ[envvar])
|
||||
|
||||
|
||||
Completion help strings (ZSH only)
|
||||
----------------------------------
|
||||
Completion help strings
|
||||
-----------------------
|
||||
|
||||
ZSH supports showing documentation strings for completions. These are taken
|
||||
from the help parameters of options and subcommands. For dynamically generated
|
||||
completions a help string can be provided by returning a tuple instead of a
|
||||
string. The first element of the tuple is the completion and the second is the
|
||||
help string to display.
|
||||
ZSH and fish support showing documentation strings for completions.
|
||||
These are taken from the help parameters of options and subcommands. For
|
||||
dynamically generated completions a help string can be provided by
|
||||
returning a tuple instead of a string. The first element of the tuple is
|
||||
the completion and the second is the help string to display.
|
||||
|
||||
Here is an example of using a callback function to generate dynamic suggestions with help strings:
|
||||
Here is an example of using a callback function to generate dynamic
|
||||
suggestions with help strings:
|
||||
|
||||
.. click:example::
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
def get_colors(ctx, args, incomplete):
|
||||
colors = [('red', 'help string for the color red'),
|
||||
('blue', 'help string for the color blue'),
|
||||
('green', 'help string for the color green')]
|
||||
colors = [('red', 'a warm color'),
|
||||
('blue', 'a cool color'),
|
||||
('green', 'the other starter color')]
|
||||
return [c for c in colors if incomplete in c[0]]
|
||||
|
||||
@click.command()
|
||||
|
@ -85,47 +85,79 @@ Here is an example of using a callback function to generate dynamic suggestions
|
|||
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.
|
||||
In order to activate shell completion, you need to inform your shell
|
||||
that completion is available for your script. Any Click application
|
||||
automatically provides support for that. If the program is executed with
|
||||
a special ``_<PROG_NAME>_COMPLETE`` variable, the completion mechanism
|
||||
is triggered instead of the normal command. ``<PROG_NAME>`` is the
|
||||
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 trivially activated.
|
||||
If your tool is called ``foo-bar``, then the variable is called
|
||||
``_FOO_BAR_COMPLETE``. By exporting it with the ``source_{shell}``
|
||||
value it will output the activation script to evaluate.
|
||||
|
||||
For instance, to enable Bash completion for your ``foo-bar`` script, this
|
||||
is what you would need to put into your ``.bashrc``::
|
||||
Here are examples for a ``foo-bar`` script.
|
||||
|
||||
eval "$(_FOO_BAR_COMPLETE=source foo-bar)"
|
||||
For Bash, add this to ``~/.bashrc``:
|
||||
|
||||
For zsh users add this to your ``.zshrc``::
|
||||
.. code-block:: text
|
||||
|
||||
eval "$(_FOO_BAR_COMPLETE=source_bash foo-bar)"
|
||||
|
||||
For Zsh, add this to ``~/.zshrc``:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
eval "$(_FOO_BAR_COMPLETE=source_zsh foo-bar)"
|
||||
|
||||
From this point onwards, your script will have autocompletion enabled.
|
||||
For Fish, add this to ``~/.config/fish/completions/foo-bar.fish``:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
eval (env _FOO_BAR_COMPLETE=source_fish foo-bar)
|
||||
|
||||
Open a new shell to enable completion. Or run the ``eval`` command
|
||||
directly in your current shell to enable it temporarily.
|
||||
|
||||
|
||||
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.
|
||||
The above ``eval`` examples will invoke your application every time a
|
||||
shell is started. This may slow down shell startup time significantly.
|
||||
|
||||
This can be easily accomplished::
|
||||
Alternatively, export the generated completion code as a static script
|
||||
to be executed. You can ship this file with your builds; tools like Git
|
||||
do this. At least Zsh will also cache the results of completion files,
|
||||
but not ``eval`` scripts.
|
||||
|
||||
_FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh
|
||||
For Bash:
|
||||
|
||||
For zsh:
|
||||
.. code-block:: text
|
||||
|
||||
_FOO_BAR_COMPLETE=source_bash foo-bar > foo-bar-complete.sh
|
||||
|
||||
For Zsh:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
_FOO_BAR_COMPLETE=source_zsh foo-bar > foo-bar-complete.sh
|
||||
|
||||
And then you would put this into your .bashrc or .zshrc instead::
|
||||
For Fish:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
_FOO_BAR_COMPLETE=source_zsh foo-bar > foo-bar-complete.sh
|
||||
|
||||
In ``.bashrc`` or ``.zshrc``, source the script instead of the ``eval``
|
||||
command:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
. /path/to/foo-bar-complete.sh
|
||||
|
||||
For Fish, add the file to the completions directory:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
_FOO_BAR_COMPLETE=source_fish foo-bar > ~/.config/fish/completions/foo-bar-complete.fish
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
.. currentmodule:: click
|
||||
Changes
|
||||
=======
|
||||
|
||||
.. include:: ../CHANGES.rst
|
||||
|
|
|
@ -27,7 +27,7 @@ when an inner command runs:
|
|||
def cli(debug):
|
||||
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
|
||||
|
||||
@cli.command()
|
||||
@cli.command() # @cli, not @click!
|
||||
def sync():
|
||||
click.echo('Syncing')
|
||||
|
||||
|
@ -88,7 +88,7 @@ script like this:
|
|||
@click.pass_context
|
||||
def cli(ctx, debug):
|
||||
# ensure that ctx.obj exists and is a dict (in case `cli()` is called
|
||||
# by means other than the `if` block below
|
||||
# by means other than the `if` block below)
|
||||
ctx.ensure_object(dict)
|
||||
|
||||
ctx.obj['DEBUG'] = debug
|
||||
|
@ -317,7 +317,8 @@ When using multi command chaining you can only have one command (the last)
|
|||
use ``nargs=-1`` on an argument. It is also not possible to nest multi
|
||||
commands below chained multicommands. Other than that there are no
|
||||
restrictions on how they work. They can accept options and arguments as
|
||||
normal.
|
||||
normal. The order between options and arguments is limited for chained
|
||||
commands. Currently only ``--options argument`` order is allowed.
|
||||
|
||||
Another note: the :attr:`Context.invoked_subcommand` attribute is a bit
|
||||
useless for multi commands as it will give ``'*'`` as value if more than
|
||||
|
@ -439,10 +440,18 @@ 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
|
||||
overridden at any point by commands. For instance, a top-level command could
|
||||
load the defaults from a configuration file.
|
||||
The default map can be nested arbitrarily for each subcommand:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
default_map = {
|
||||
"debug": True, # default for a top level option
|
||||
"runserver": {"port": 5000} # default for a subcommand
|
||||
}
|
||||
|
||||
The default map can be provided when the script is invoked, or
|
||||
overridden at any point by commands. For instance, a top-level command
|
||||
could load the defaults from a configuration file.
|
||||
|
||||
Example usage:
|
||||
|
||||
|
|
32
docs/conf.py
32
docs/conf.py
|
@ -1,17 +1,25 @@
|
|||
from pallets_sphinx_themes import ProjectLink, get_version
|
||||
from pallets_sphinx_themes import get_version
|
||||
from pallets_sphinx_themes import ProjectLink
|
||||
|
||||
# Project --------------------------------------------------------------
|
||||
|
||||
project = "Click"
|
||||
copyright = "2014 Pallets Team"
|
||||
author = "Pallets Team"
|
||||
copyright = "2014 Pallets"
|
||||
author = "Pallets"
|
||||
release, version = get_version("Click", version_length=1)
|
||||
|
||||
# General --------------------------------------------------------------
|
||||
|
||||
master_doc = "index"
|
||||
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "pallets_sphinx_themes"]
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinxcontrib.log_cabinet",
|
||||
"pallets_sphinx_themes",
|
||||
"sphinx_issues",
|
||||
]
|
||||
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
|
||||
issues_github_path = "pallets/click"
|
||||
|
||||
# HTML -----------------------------------------------------------------
|
||||
|
||||
|
@ -21,26 +29,22 @@ html_context = {
|
|||
"project_links": [
|
||||
ProjectLink("Donate to Pallets", "https://palletsprojects.com/donate"),
|
||||
ProjectLink("Click Website", "https://palletsprojects.com/p/click/"),
|
||||
ProjectLink("PyPI releases", "https://pypi.org/project/Click/"),
|
||||
ProjectLink("PyPI releases", "https://pypi.org/project/click/"),
|
||||
ProjectLink("Source Code", "https://github.com/pallets/click/"),
|
||||
ProjectLink("Issue Tracker", "https://github.com/pallets/click/issues/"),
|
||||
]
|
||||
}
|
||||
html_sidebars = {
|
||||
"index": ["project.html", "versions.html", "searchbox.html"],
|
||||
"**": ["localtoc.html", "relations.html", "versions.html", "searchbox.html"],
|
||||
"index": ["project.html", "localtoc.html", "searchbox.html"],
|
||||
"**": ["localtoc.html", "relations.html", "searchbox.html"],
|
||||
}
|
||||
singlehtml_sidebars = {"index": ["project.html", "versions.html", "localtoc.html"]}
|
||||
singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
|
||||
html_static_path = ["_static"]
|
||||
html_favicon = "_static/click-icon.png"
|
||||
html_logo = "_static/click-logo-sidebar.png"
|
||||
html_title = "Click Documentation ({})".format(version)
|
||||
html_title = f"Click Documentation ({version})"
|
||||
html_show_sourcelink = False
|
||||
html_domain_indices = False
|
||||
html_experimental_html5_writer = True
|
||||
|
||||
# LaTeX ----------------------------------------------------------------
|
||||
|
||||
latex_documents = [
|
||||
(master_doc, "Click-{}.tex".format(version), html_title, author, "manual")
|
||||
]
|
||||
latex_documents = [(master_doc, f"Click-{version}.tex", html_title, author, "manual")]
|
||||
|
|
|
@ -32,10 +32,54 @@ And what it looks like:
|
|||
|
||||
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.
|
||||
|
||||
.. _documenting-arguments:
|
||||
|
||||
Documenting Arguments
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:func:`click.argument` does not take a ``help`` parameter. 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 command help text
|
||||
by referring to them by name.
|
||||
|
||||
You might prefer to reference the argument in the description:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('filename')
|
||||
def touch(filename):
|
||||
"""Print FILENAME."""
|
||||
click.echo(filename)
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(touch, args=['--help'])
|
||||
|
||||
Or you might prefer to explicitly provide a description of the argument:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.argument('filename')
|
||||
def touch(filename):
|
||||
"""Print FILENAME.
|
||||
|
||||
FILENAME is the name of the file to check.
|
||||
"""
|
||||
click.echo(filename)
|
||||
|
||||
And what it looks like:
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(touch, args=['--help'])
|
||||
|
||||
For more examples, see the examples in :doc:`/arguments`.
|
||||
|
||||
|
||||
Preventing Rewrapping
|
||||
---------------------
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
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
|
||||
------------
|
||||
BSD-3-Clause License
|
||||
====================
|
||||
|
||||
.. include:: ../LICENSE.rst
|
||||
|
||||
|
|
|
@ -13,25 +13,45 @@ distinct from :ref:`positional arguments <arguments>`.
|
|||
Name Your Options
|
||||
-----------------
|
||||
|
||||
The naming rules can be found in :ref:`parameter_names`. In short, you
|
||||
can refer the option **implicitly** by the longest dash-prefixed argument:
|
||||
Options have a name that will be used as the Python argument name when
|
||||
calling the decorated function. This can be inferred from the option
|
||||
names or given explicitly. Names are given as position arguments to the
|
||||
decorator.
|
||||
|
||||
.. click:example::
|
||||
A name is chosen in the following order
|
||||
|
||||
1. If a name is not prefixed, it is used as the Python argument name
|
||||
and not treated as an option name on the command line.
|
||||
2. If there is at least one name prefixed with two dashes, the first
|
||||
one given is used as the name.
|
||||
3. The first name prefixed with one dash is used otherwise.
|
||||
|
||||
To get the Python argument name, the chosen name is converted to lower
|
||||
case, up to two dashes are removed as the prefix, and other dashes are
|
||||
converted to underscores.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@click.command()
|
||||
@click.option('-s', '--string-to-echo')
|
||||
def echo(string_to_echo):
|
||||
click.echo(string_to_echo)
|
||||
|
||||
Or, **explicitly**, by giving one non-dash-prefixed argument:
|
||||
|
||||
.. click:example::
|
||||
.. code-block:: python
|
||||
|
||||
@click.command()
|
||||
@click.option('-s', '--string-to-echo', 'string')
|
||||
def echo(string):
|
||||
click.echo(string)
|
||||
|
||||
- ``"-f", "--foo-bar"``, the name is ``foo_bar``
|
||||
- ``"-x"``, the name is ``x``
|
||||
- ``"-f", "--filename", "dest"``, the name is ``dest``
|
||||
- ``"--CamelCase"``, the name is ``camelcase``
|
||||
- ``"-f", "-fb"``, the name is ``f``
|
||||
- ``"--f", "--foo-bar"``, the name is ``f``
|
||||
- ``"---f"``, the name is ``_f``
|
||||
|
||||
Basic Value Options
|
||||
-------------------
|
||||
|
||||
|
@ -145,11 +165,13 @@ used. The above example is thus equivalent to this:
|
|||
def putitem(item):
|
||||
click.echo('name=%s id=%d' % item)
|
||||
|
||||
.. _multiple-options:
|
||||
|
||||
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 --
|
||||
parameter being provided multiple times and have all the 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:
|
||||
|
@ -169,6 +191,15 @@ And on the command line:
|
|||
|
||||
invoke(commit, args=['-m', 'foo', '-m', 'bar'])
|
||||
|
||||
When passing a ``default`` with ``multiple=True``, the default value
|
||||
must be a list or tuple, otherwise it will be interpreted as a list of
|
||||
single characters.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@click.option("--format", multiple=True, default=["json"])
|
||||
|
||||
|
||||
Counting
|
||||
--------
|
||||
|
||||
|
@ -315,14 +346,17 @@ 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.
|
||||
with a list of valid values. The originally passed choice will be returned,
|
||||
not the str passed on the command line. Token normalization functions and
|
||||
``case_sensitive=False`` can cause the two to be different but still match.
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--hash-type', type=click.Choice(['md5', 'sha1']))
|
||||
@click.option('--hash-type',
|
||||
type=click.Choice(['MD5', 'SHA1'], case_sensitive=False))
|
||||
def digest(hash_type):
|
||||
click.echo(hash_type)
|
||||
|
||||
|
@ -330,16 +364,27 @@ What it looks like:
|
|||
|
||||
.. click:run::
|
||||
|
||||
invoke(digest, args=['--hash-type=MD5'])
|
||||
println()
|
||||
invoke(digest, args=['--hash-type=md5'])
|
||||
println()
|
||||
invoke(digest, args=['--hash-type=foo'])
|
||||
println()
|
||||
invoke(digest, args=['--help'])
|
||||
|
||||
.. note::
|
||||
Only pass the choices as list or tuple. Other iterables (like
|
||||
generators) may lead to unexpected results.
|
||||
|
||||
You should only pass the choices as list or tuple. Other iterables (like
|
||||
generators) may lead to surprising results.
|
||||
Choices work with options that have ``multiple=True``. If a ``default``
|
||||
value is given with ``multiple=True``, it should be a list or tuple of
|
||||
valid choices.
|
||||
|
||||
Choices should be unique after considering the effects of
|
||||
``case_sensitive`` and any specified token normalization function.
|
||||
|
||||
.. versionchanged:: 7.1
|
||||
The resulting value from an option will always be one of the
|
||||
originally passed choices regardless of ``case_sensitive``.
|
||||
|
||||
.. _option-prompting:
|
||||
|
||||
|
@ -382,6 +427,10 @@ What it looks like:
|
|||
|
||||
invoke(hello, input=['John'])
|
||||
|
||||
It is advised that prompt not be used in conjunction with the multiple
|
||||
flag set to True. Instead, prompt in the function interactively.
|
||||
|
||||
|
||||
Password Prompts
|
||||
----------------
|
||||
|
||||
|
@ -566,8 +615,8 @@ 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``.
|
||||
called ``run`` taking an option called ``reload`` and the prefix is
|
||||
``WEB``, then the variable is ``WEB_RUN_RELOAD``.
|
||||
|
||||
Example usage:
|
||||
|
||||
|
@ -588,8 +637,11 @@ And from the command line:
|
|||
invoke(greet, env={'GREETER_USERNAME': 'john'},
|
||||
auto_envvar_prefix='GREETER')
|
||||
|
||||
When using ``auto_envvar_prefix`` with command groups, the command name needs
|
||||
to be included in the environment variable, between the prefix and the parameter name, *i.e.* *PREFIX_COMMAND_VARIABLE*.
|
||||
When using ``auto_envvar_prefix`` with command groups, the command name
|
||||
needs to be included in the environment variable, between the prefix and
|
||||
the parameter name, *i.e.* ``PREFIX_COMMAND_VARIABLE``. If you have a
|
||||
subcommand called ``run-server`` taking an option called ``host`` and
|
||||
the prefix is ``WEB``, then the variable is ``WEB_RUN_SERVER_HOST``.
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
@ -23,12 +23,13 @@ available for options:
|
|||
* 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)
|
||||
(:ref:`this is intentional <documenting-arguments>` 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).
|
||||
arguments (defaults to 1), or they may be specified multiple times using
|
||||
:ref:`multiple-options`.
|
||||
|
||||
Parameter Types
|
||||
---------------
|
||||
|
@ -82,58 +83,60 @@ fails with a `ValueError` is also supported, though discouraged.
|
|||
Parameter Names
|
||||
---------------
|
||||
|
||||
Parameters (both options and arguments) accept a number of positional arguments
|
||||
which are passed to the command function as parameters. Each string with a
|
||||
single dash is added as a short argument; each string starting with a double
|
||||
dash as a long one.
|
||||
Parameters (both options and arguments) have a name that will be used as
|
||||
the Python argument name when calling the decorated function with
|
||||
values.
|
||||
|
||||
If a string is added without any dashes, it becomes the internal parameter name
|
||||
which is also used as variable name.
|
||||
Arguments take only one positional name. To provide a different name for
|
||||
use in help text, see :ref:`doc-meta-variables`.
|
||||
|
||||
If all names for a parameter contain dashes, the internal name is generated
|
||||
automatically by taking the longest argument and converting all dashes to
|
||||
underscores.
|
||||
Options can have many names that may be prefixed with one or two dashes.
|
||||
Names with one dash are parsed as short options, names with two are
|
||||
parsed as long options. If a name is not prefixed, it is used as the
|
||||
Python argument name and not parsed as an option name. Otherwise, the
|
||||
first name with a two dash prefix is used, or the first with a one dash
|
||||
prefix if there are none with two. The prefix is removed and dashes are
|
||||
converted to underscores to get the Python argument name.
|
||||
|
||||
The internal name is converted to lowercase.
|
||||
|
||||
Examples:
|
||||
|
||||
* For an option with ``('-f', '--foo-bar')``, the parameter name is `foo_bar`.
|
||||
* For an option with ``('-x',)``, the parameter is `x`.
|
||||
* For an option with ``('-f', '--filename', 'dest')``, the parameter name is `dest`.
|
||||
* For an option with ``('--CamelCaseOption',)``, the parameter is `camelcaseoption`.
|
||||
* For an arguments with ``(`foogle`)``, the parameter name is `foogle`. To
|
||||
provide a different human readable name for use in help text, see the section
|
||||
about :ref:`doc-meta-variables`.
|
||||
|
||||
Implementing Custom Types
|
||||
-------------------------
|
||||
|
||||
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.
|
||||
class. Override the :meth:`~ParamType.convert` method to convert the
|
||||
value from a string to the correct type.
|
||||
|
||||
The following code implements an integer type that accepts hex and octal
|
||||
numbers in addition to normal integers, and converts them into regular
|
||||
integers::
|
||||
integers.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
|
||||
class BasedIntParamType(click.ParamType):
|
||||
name = 'integer'
|
||||
name = "integer"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
if value[:2].lower() == '0x':
|
||||
if value[:2].lower() == "0x":
|
||||
return int(value[2:], 16)
|
||||
elif value[:1] == '0':
|
||||
elif value[:1] == "0":
|
||||
return int(value, 8)
|
||||
return int(value, 10)
|
||||
except TypeError:
|
||||
self.fail(
|
||||
"expected string for int() conversion, got "
|
||||
f"{value!r} of type {type(value).__name__}",
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
except ValueError:
|
||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||
self.fail(f"{value!r} is not a valid integer", 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.
|
||||
The :attr:`~ParamType.name` attribute is optional and is used for
|
||||
documentation. Call :meth:`~ParamType.fail` if conversion fails. The
|
||||
``param`` and ``ctx`` arguments may be ``None`` in some cases such as
|
||||
prompts.
|
||||
|
|
|
@ -8,9 +8,6 @@ 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.4 or higher.
|
||||
|
||||
At the moment, it is strongly recommended to use Python 2 for Click
|
||||
utilities unless Python 3 is a hard requirement.
|
||||
|
||||
.. _python3-limitations:
|
||||
|
||||
Python 3 Limitations
|
||||
|
@ -123,6 +120,12 @@ If you see something like this error in Python 3::
|
|||
to Python 2 or consult the Python 3 section of the docs for
|
||||
mitigation steps.
|
||||
|
||||
.. note::
|
||||
|
||||
In Python 3.7 and later you will no longer get a ``RuntimeError`` in
|
||||
many cases thanks to :pep:`538` and :pep:`540`, which changed the
|
||||
default assumption in unconfigured environments.
|
||||
|
||||
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.
|
||||
|
@ -150,15 +153,20 @@ 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>`_
|
||||
<https://bugs.python.org/issue13643#msg149941>`_
|
||||
* `Use surrogateescape as default error handler
|
||||
<http://bugs.python.org/issue19977>`_
|
||||
<https://bugs.python.org/issue19977>`_
|
||||
* `Python 3 raises Unicode errors in the C locale
|
||||
<http://bugs.python.org/issue19846>`_
|
||||
<https://bugs.python.org/issue19846>`_
|
||||
* `LC_CTYPE=C: pydoc leaves terminal in an unusable state
|
||||
<http://bugs.python.org/issue21398>`_ (this is relevant to Click
|
||||
<https://bugs.python.org/issue21398>`_ (this is relevant to Click
|
||||
because the pager support is provided by the stdlib pydoc module)
|
||||
|
||||
Note (Python 3.7 onwards): Even though your locale may not be properly
|
||||
configured, Python 3.7 Click will not raise the above exception because Python
|
||||
3.7 programs are better at choosing default locales. This doesn't change the
|
||||
general issue that your locale may be misconfigured.
|
||||
|
||||
Unicode Literals
|
||||
----------------
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ commands will work for you::
|
|||
|
||||
or even better::
|
||||
|
||||
$ sudo pip install virtualenv
|
||||
$ pip install virtualenv --user
|
||||
|
||||
One of these will probably install virtualenv on your system. Maybe it's even
|
||||
in your package manager. If you use Ubuntu, try::
|
||||
|
@ -51,7 +51,7 @@ If you are on Windows (or none of the above methods worked) you must install
|
|||
Once you have it installed, run the ``pip`` command from above, but without
|
||||
the `sudo` prefix.
|
||||
|
||||
.. _installing pip: https://pip.readthedocs.io/en/latest/installing.html
|
||||
.. _installing pip: https://pip.readthedocs.io/en/latest/installing/
|
||||
|
||||
Once you have virtualenv installed, just fire up a shell and create
|
||||
your own environment. I usually create a project folder and a `venv`
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
Sphinx~=1.8.0
|
||||
Pallets-Sphinx-Themes~=1.1.0
|
||||
Sphinx~=2.4.4
|
||||
Pallets-Sphinx-Themes~=1.2.3
|
||||
sphinxcontrib-log-cabinet~=1.0.1
|
||||
sphinx-issues~=1.2.0
|
||||
|
|
155
docs/testing.rst
155
docs/testing.rst
|
@ -20,48 +20,61 @@ The basic functionality for testing Click applications is the
|
|||
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.
|
||||
data, exit code, and optional exception attached:
|
||||
|
||||
Example::
|
||||
.. code-block:: python
|
||||
:caption: hello.py
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
import click
|
||||
|
||||
def test_hello_world():
|
||||
@click.command()
|
||||
@click.argument('name')
|
||||
def hello(name):
|
||||
click.echo('Hello %s!' % name)
|
||||
@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'
|
||||
.. code-block:: python
|
||||
:caption: test_hello.py
|
||||
|
||||
For subcommand testing, a subcommand name must be specified in the `args` parameter of :meth:`CliRunner.invoke` method.
|
||||
from click.testing import CliRunner
|
||||
from hello import hello
|
||||
|
||||
Example::
|
||||
def test_hello_world():
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(hello, ['Peter'])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'Hello Peter!\n'
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
|
||||
def test_sync():
|
||||
@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('Syncing')
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--debug', 'sync'])
|
||||
assert result.exit_code == 0
|
||||
assert 'Debug mode is on' in result.output
|
||||
assert 'Syncing' in result.output
|
||||
For subcommand testing, a subcommand name must be specified in the `args` parameter of :meth:`CliRunner.invoke` method:
|
||||
|
||||
Additional keyword arguments passed to ``.invoke()`` will be used to construct the initial Context object. For example, if you want to run your tests against a fixed terminal width you can use the following::
|
||||
.. code-block:: python
|
||||
:caption: sync.py
|
||||
|
||||
import click
|
||||
|
||||
@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('Syncing')
|
||||
|
||||
.. code-block:: python
|
||||
:caption: test_sync.py
|
||||
|
||||
from click.testing import CliRunner
|
||||
from sync import cli
|
||||
|
||||
def test_sync():
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--debug', 'sync'])
|
||||
assert result.exit_code == 0
|
||||
assert 'Debug mode is on' in result.output
|
||||
assert 'Syncing' in result.output
|
||||
|
||||
Additional keyword arguments passed to ``.invoke()`` will be used to construct the initial Context object.
|
||||
For example, if you want to run your tests against a fixed terminal width you can use the following::
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--debug', 'sync'], terminal_width=60)
|
||||
|
@ -69,49 +82,63 @@ Additional keyword arguments passed to ``.invoke()`` will be used to construct t
|
|||
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.
|
||||
For basic command line tools with file system operations, the
|
||||
:meth:`CliRunner.isolated_filesystem` method is useful for setting the
|
||||
current working directory to a new, empty folder.
|
||||
|
||||
Example::
|
||||
.. code-block:: python
|
||||
:caption: cat.py
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
import click
|
||||
|
||||
def test_cat():
|
||||
@click.command()
|
||||
@click.argument('f', type=click.File())
|
||||
def cat(f):
|
||||
click.echo(f.read())
|
||||
@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', 'w') as f:
|
||||
f.write('Hello World!')
|
||||
.. code-block:: python
|
||||
:caption: test_cat.py
|
||||
|
||||
result = runner.invoke(cat, ['hello.txt'])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'Hello World!\n'
|
||||
from click.testing import CliRunner
|
||||
from cat import cat
|
||||
|
||||
def test_cat():
|
||||
runner = CliRunner()
|
||||
with runner.isolated_filesystem():
|
||||
with open('hello.txt', 'w') 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::
|
||||
stream (stdin). This is very useful for testing prompts, for instance:
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
.. code-block:: python
|
||||
:caption: prompt.py
|
||||
|
||||
def test_prompts():
|
||||
@click.command()
|
||||
@click.option('--foo', prompt=True)
|
||||
def test(foo):
|
||||
click.echo('foo=%s' % foo)
|
||||
import click
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(test, input='wau wau\n')
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo: wau wau\nfoo=wau wau\n'
|
||||
@click.command()
|
||||
@click.option('--foo', prompt=True)
|
||||
def prompt(foo):
|
||||
click.echo('foo=%s' % foo)
|
||||
|
||||
.. code-block:: python
|
||||
:caption: test_prompt.py
|
||||
|
||||
from click.testing import CliRunner
|
||||
from prompt import prompt
|
||||
|
||||
def test_prompts():
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(prompt, 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
|
||||
|
|
|
@ -6,6 +6,37 @@ 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-7.0:
|
||||
|
||||
Upgrading to 7.0
|
||||
----------------
|
||||
|
||||
Commands that take their name from the decorated function now replace
|
||||
underscores with dashes. For example, the Python function ``run_server``
|
||||
will get the command name ``run-server`` now. There are a few options
|
||||
to address this:
|
||||
|
||||
- To continue with the new behavior, pin your dependency to
|
||||
``Click>=7`` and update any documentation to use dashes.
|
||||
- To keep existing behavior, add an explicit command name with
|
||||
underscores, like ``@click.command("run_server")``.
|
||||
- To try a name with dashes if the name with underscores was not
|
||||
found, pass a ``token_normalize_func`` to the context:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def normalize(name):
|
||||
return name.replace("_", "-")
|
||||
|
||||
@click.group(context_settings={"token_normalize_func": normalize})
|
||||
def group():
|
||||
...
|
||||
|
||||
@group.command()
|
||||
def run_server():
|
||||
...
|
||||
|
||||
|
||||
.. _upgrade-to-3.2:
|
||||
|
||||
Upgrading to 3.2
|
||||
|
|
|
@ -33,7 +33,7 @@ suppressed by passing ``nl=False``::
|
|||
Last but not least :func:`echo` uses click's intelligent internal output
|
||||
streams to stdout and stderr which support unicode output on the Windows
|
||||
console. This means for as long as you are using `click.echo` you can
|
||||
output unicode character (there are some limitations on the default font
|
||||
output unicode characters (there are some limitations on the default font
|
||||
with regards to which characters can be displayed). This functionality is
|
||||
new in Click 6.0.
|
||||
|
||||
|
@ -211,7 +211,7 @@ 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.
|
||||
will be ``None``, otherwise the entered text.
|
||||
|
||||
Example usage::
|
||||
|
||||
|
@ -389,6 +389,16 @@ but you know the length, you can explicitly provide it::
|
|||
for user in bar:
|
||||
modify_the_user(user)
|
||||
|
||||
Note that :func:`progressbar` updates the bar *after* each iteration of the
|
||||
loop. So code like this will render correctly::
|
||||
|
||||
import time
|
||||
|
||||
with click.progressbar([1, 2, 3]) as bar:
|
||||
for x in bar:
|
||||
print('sleep({})...'.format(x))
|
||||
time.sleep(x)
|
||||
|
||||
Another useful feature is to associate a label with the progress bar which
|
||||
will be shown preceding the progress bar::
|
||||
|
||||
|
|
83
docs/why.rst
83
docs/why.rst
|
@ -10,7 +10,7 @@ line utility for Python out there which ticks the following boxes:
|
|||
* is lazily composable without restrictions
|
||||
* supports implementation of Unix/POSIX command line conventions
|
||||
* supports loading values from environment variables out of the box
|
||||
* supports for prompting of custom values
|
||||
* support 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
|
||||
|
@ -18,56 +18,51 @@ line utility for Python out there which ticks the following boxes:
|
|||
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.
|
||||
There are many alternatives to Click; the obvious ones are ``optparse``
|
||||
and ``argparse`` from the standard library. Have a look to see if something
|
||||
else resonates with you.
|
||||
|
||||
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 actually implements its own parsing of arguments and does not use
|
||||
``optparse`` or ``argparse`` following the ``optparse`` parsing behavior.
|
||||
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.
|
||||
Click is designed to be fun and customizable but not overly flexible.
|
||||
For instance, the customizability of help pages is constrained. This
|
||||
constraint is intentional because Click promises multiple Click instances
|
||||
will continue to function as intended when strung together.
|
||||
|
||||
Too much customizability would break this promise.
|
||||
|
||||
Click was written to support the `Flask <http://flask.pocoo.org/>`_
|
||||
Click was written to support the `Flask <https://palletsprojects.com/p/flask/>`_
|
||||
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.
|
||||
looking at the :ref:`complex-guide` chapter.
|
||||
|
||||
Why not Argparse?
|
||||
-----------------
|
||||
|
||||
Click is internally based on optparse instead of argparse. This however
|
||||
Click is internally based on ``optparse`` instead of ``argparse``. This
|
||||
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:
|
||||
with. Click is not based on argparse because it has some 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 interspersed
|
||||
arguments. Without this feature it's not possible to safely implement
|
||||
Click's nested parsing nature.
|
||||
* argparse has built-in behavior to guess if something is an
|
||||
argument or an option. This becomes a problem when dealing with
|
||||
incomplete command lines; the behaviour becomes unpredictable
|
||||
without full knowledge of a command line. This goes against Click's
|
||||
ambitions of dispatching to subparsers.
|
||||
* argparse does not support disabling interspersed arguments. Without
|
||||
this feature, it's not possible to safely implement Click's nested
|
||||
parsing.
|
||||
|
||||
Why not Docopt etc.?
|
||||
--------------------
|
||||
|
||||
Docopt and many tools like it are cool in how they work, but very few of
|
||||
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
|
||||
|
@ -78,13 +73,13 @@ 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
|
||||
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 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.
|
||||
|
||||
|
@ -95,25 +90,25 @@ 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
|
||||
- Click has strong information available for all parameters and commands,
|
||||
so it can generate unified help pages for the full CLI and
|
||||
assist the user in converting the input data as necessary.
|
||||
- Click has a strong understanding of what types are and can give the user
|
||||
- Click has a strong understanding of what types are, and it 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.
|
||||
different error message 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
|
||||
to evolve over time and 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
|
||||
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.
|
||||
achieve unified command line interfaces. For instance, as a developer, you
|
||||
are given very little choice in formatting your help pages.
|
||||
|
||||
|
||||
Why Hardcoded Behaviors?
|
||||
|
@ -125,7 +120,7 @@ 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
|
||||
accepting an 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
|
||||
|
@ -144,7 +139,7 @@ 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
|
||||
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
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import os
|
||||
|
||||
import click
|
||||
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
except ImportError:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
|
||||
class Config(object):
|
||||
|
@ -14,14 +15,25 @@ class Config(object):
|
|||
self.path = os.getcwd()
|
||||
self.aliases = {}
|
||||
|
||||
def add_alias(self, alias, cmd):
|
||||
self.aliases.update({alias: cmd})
|
||||
|
||||
def read_config(self, filename):
|
||||
parser = configparser.RawConfigParser()
|
||||
parser.read([filename])
|
||||
try:
|
||||
self.aliases.update(parser.items('aliases'))
|
||||
self.aliases.update(parser.items("aliases"))
|
||||
except configparser.NoSectionError:
|
||||
pass
|
||||
|
||||
def write_config(self, filename):
|
||||
parser = configparser.RawConfigParser()
|
||||
parser.add_section("aliases")
|
||||
for key, value in self.aliases.items():
|
||||
parser.set("aliases", key, value)
|
||||
with open(filename, "wb") as file:
|
||||
parser.write(file)
|
||||
|
||||
|
||||
pass_config = click.make_pass_decorator(Config, ensure=True)
|
||||
|
||||
|
@ -50,13 +62,14 @@ class AliasedGroup(click.Group):
|
|||
# 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())]
|
||||
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)))
|
||||
ctx.fail("Too many matches: {}".format(", ".join(sorted(matches))))
|
||||
|
||||
|
||||
def read_config(ctx, param, value):
|
||||
|
@ -67,15 +80,19 @@ def read_config(ctx, param, value):
|
|||
"""
|
||||
cfg = ctx.ensure_object(Config)
|
||||
if value is None:
|
||||
value = os.path.join(os.path.dirname(__file__), 'aliases.ini')
|
||||
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.')
|
||||
@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."""
|
||||
|
||||
|
@ -83,29 +100,43 @@ def cli():
|
|||
@cli.command()
|
||||
def push():
|
||||
"""Pushes changes."""
|
||||
click.echo('Push')
|
||||
click.echo("Push")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def pull():
|
||||
"""Pulls changes."""
|
||||
click.echo('Pull')
|
||||
click.echo("Pull")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def clone():
|
||||
"""Clones a repository."""
|
||||
click.echo('Clone')
|
||||
click.echo("Clone")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def commit():
|
||||
"""Commits pending changes."""
|
||||
click.echo('Commit')
|
||||
click.echo("Commit")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@pass_config
|
||||
def status(config):
|
||||
"""Shows the status."""
|
||||
click.echo('Status for %s' % config.path)
|
||||
click.echo("Status for {}".format(config.path))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@pass_config
|
||||
@click.argument("alias_", metavar="ALIAS", type=click.STRING)
|
||||
@click.argument("cmd", type=click.STRING)
|
||||
@click.option(
|
||||
"--config_file", type=click.Path(exists=True, dir_okay=False), default="aliases.ini"
|
||||
)
|
||||
def alias(config, alias_, cmd, config_file):
|
||||
"""Adds an alias to the specified configuration file."""
|
||||
config.add_alias(alias_, cmd)
|
||||
config.write_config(config_file)
|
||||
click.echo("Added '{}' as alias for '{}'".format(alias_, cmd))
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-aliases',
|
||||
version='1.0',
|
||||
py_modules=['aliases'],
|
||||
name="click-example-aliases",
|
||||
version="1.0",
|
||||
py_modules=["aliases"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
aliases=aliases:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import click
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
|
@ -14,14 +15,14 @@ def get_env_vars(ctx, args, incomplete):
|
|||
yield key
|
||||
|
||||
|
||||
@cli.command(help='A command to print environment variables')
|
||||
@cli.command(help="A command to print environment variables")
|
||||
@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)
|
||||
def cmd1(envvar):
|
||||
click.echo('Environment variable: %s' % envvar)
|
||||
click.echo('Value: %s' % os.environ[envvar])
|
||||
click.echo("Environment variable: {}".format(envvar))
|
||||
click.echo("Value: {}".format(os.environ[envvar]))
|
||||
|
||||
|
||||
@click.group(help='A group that holds a subcommand')
|
||||
@click.group(help="A group that holds a subcommand")
|
||||
def group():
|
||||
pass
|
||||
|
||||
|
@ -29,17 +30,16 @@ def group():
|
|||
def list_users(ctx, args, incomplete):
|
||||
# You can generate completions with descriptions by returning
|
||||
# tuples in the form (completion, description).
|
||||
users = [('bob', 'butcher'),
|
||||
('alice', 'baker'),
|
||||
('jerry', 'candlestick maker')]
|
||||
# Ths will allow completion matches based on matches within the description string too!
|
||||
users = [("bob", "butcher"), ("alice", "baker"), ("jerry", "candlestick maker")]
|
||||
# Ths will allow completion matches based on matches within the
|
||||
# description string too!
|
||||
return [user for user in users if incomplete in user[0] or incomplete in user[1]]
|
||||
|
||||
|
||||
@group.command(help='Choose a user')
|
||||
@group.command(help="Choose a user")
|
||||
@click.argument("user", type=click.STRING, autocompletion=list_users)
|
||||
def subcmd(user):
|
||||
click.echo('Chosen user is %s' % user)
|
||||
click.echo("Chosen user is {}".format(user))
|
||||
|
||||
|
||||
cli.add_command(group)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-bashcompletion',
|
||||
version='1.0',
|
||||
py_modules=['bashcompletion'],
|
||||
name="click-example-bashcompletion",
|
||||
version="1.0",
|
||||
py_modules=["bashcompletion"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
bashcompletion=bashcompletion:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
import click
|
||||
|
||||
|
||||
all_colors = 'black', 'red', 'green', 'yellow', 'blue', 'magenta', \
|
||||
'cyan', 'white', 'bright_black', 'bright_red', \
|
||||
'bright_green', 'bright_yellow', 'bright_blue', \
|
||||
'bright_magenta', 'bright_cyan', 'bright_white'
|
||||
all_colors = (
|
||||
"black",
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"blue",
|
||||
"magenta",
|
||||
"cyan",
|
||||
"white",
|
||||
"bright_black",
|
||||
"bright_red",
|
||||
"bright_green",
|
||||
"bright_yellow",
|
||||
"bright_blue",
|
||||
"bright_magenta",
|
||||
"bright_cyan",
|
||||
"bright_white",
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@ -16,13 +30,15 @@ def cli():
|
|||
Give it a try!
|
||||
"""
|
||||
for color in all_colors:
|
||||
click.echo(click.style('I am colored %s' % color, fg=color))
|
||||
click.echo(click.style("I am colored {}".format(color), fg=color))
|
||||
for color in all_colors:
|
||||
click.echo(click.style('I am colored %s and bold' % color,
|
||||
fg=color, bold=True))
|
||||
click.echo(
|
||||
click.style("I am colored {} and bold".format(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 reverse colored {}".format(color), fg=color, reverse=True)
|
||||
)
|
||||
|
||||
click.echo(click.style('I am blinking', blink=True))
|
||||
click.echo(click.style('I am underlined', underline=True))
|
||||
click.echo(click.style("I am blinking", blink=True))
|
||||
click.echo(click.style("I am underlined", underline=True))
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-colors',
|
||||
version='1.0',
|
||||
py_modules=['colors'],
|
||||
name="click-example-colors",
|
||||
version="1.0",
|
||||
py_modules=["colors"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
"click",
|
||||
# Colorama is only required for Windows.
|
||||
'colorama',
|
||||
"colorama",
|
||||
],
|
||||
entry_points='''
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
colors=colors:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import click
|
||||
|
||||
|
||||
CONTEXT_SETTINGS = dict(auto_envvar_prefix='COMPLEX')
|
||||
CONTEXT_SETTINGS = dict(auto_envvar_prefix="COMPLEX")
|
||||
|
||||
|
||||
class Context(object):
|
||||
|
||||
class Environment(object):
|
||||
def __init__(self):
|
||||
self.verbose = False
|
||||
self.home = os.getcwd()
|
||||
|
@ -24,18 +24,15 @@ class Context(object):
|
|||
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'))
|
||||
pass_environment = click.make_pass_decorator(Environment, 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_'):
|
||||
if filename.endswith(".py") and filename.startswith("cmd_"):
|
||||
rv.append(filename[4:-3])
|
||||
rv.sort()
|
||||
return rv
|
||||
|
@ -43,21 +40,23 @@ class ComplexCLI(click.MultiCommand):
|
|||
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'])
|
||||
name = name.encode("ascii", "replace")
|
||||
mod = __import__(
|
||||
"complex.commands.cmd_{}".format(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
|
||||
@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_environment
|
||||
def cli(ctx, verbose, home):
|
||||
"""A complex command line interface."""
|
||||
ctx.verbose = verbose
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from complex.cli import pass_environment
|
||||
|
||||
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
|
||||
@click.command("init", short_help="Initializes a repo.")
|
||||
@click.argument("path", required=False, type=click.Path(resolve_path=True))
|
||||
@pass_environment
|
||||
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))
|
||||
ctx.log("Initialized the repository in %s", click.format_filename(path))
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from complex.cli import pass_environment
|
||||
|
||||
import click
|
||||
from complex.cli import pass_context
|
||||
|
||||
|
||||
@click.command('status', short_help='Shows file changes.')
|
||||
@pass_context
|
||||
@click.command("status", short_help="Shows file changes.")
|
||||
@pass_environment
|
||||
def cli(ctx):
|
||||
"""Shows file changes in the current working directory."""
|
||||
ctx.log('Changed files: none')
|
||||
ctx.vlog('bla bla bla, debug info')
|
||||
ctx.log("Changed files: none")
|
||||
ctx.vlog("bla bla bla, debug info")
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-complex',
|
||||
version='1.0',
|
||||
packages=['complex', 'complex.commands'],
|
||||
name="click-example-complex",
|
||||
version="1.0",
|
||||
packages=["complex", "complex.commands"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
complex=complex.cli:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import click
|
||||
from functools import update_wrapper
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
|
||||
from PIL import Image
|
||||
from PIL import ImageEnhance
|
||||
from PIL import ImageFilter
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.group(chain=True)
|
||||
|
@ -39,10 +43,13 @@ 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)
|
||||
|
||||
|
||||
|
@ -50,12 +57,14 @@ 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)
|
||||
|
||||
|
||||
|
@ -64,9 +73,15 @@ def copy_filename(new, old):
|
|||
return new
|
||||
|
||||
|
||||
@cli.command('open')
|
||||
@click.option('-i', '--image', 'images', type=click.Path(),
|
||||
multiple=True, help='The image file to open.')
|
||||
@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
|
||||
|
@ -74,47 +89,52 @@ def open_cmd(images):
|
|||
"""
|
||||
for image in images:
|
||||
try:
|
||||
click.echo('Opening "%s"' % image)
|
||||
if image == '-':
|
||||
click.echo("Opening '{}'".format(image))
|
||||
if image == "-":
|
||||
img = Image.open(click.get_binary_stdin())
|
||||
img.filename = '-'
|
||||
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)
|
||||
click.echo("Could not open image '{}': {}".format(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)
|
||||
@cli.command("save")
|
||||
@click.option(
|
||||
"--filename",
|
||||
default="processed-{:04}.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))
|
||||
fn = filename.format(idx + 1)
|
||||
click.echo("Saving '{}' as '{}'".format(image.filename, fn))
|
||||
yield image.save(fn)
|
||||
except Exception as e:
|
||||
click.echo('Could not save image "%s": %s' %
|
||||
(image.filename, e), err=True)
|
||||
click.echo(
|
||||
"Could not save image '{}': {}".format(image.filename, e), err=True
|
||||
)
|
||||
|
||||
|
||||
@cli.command('display')
|
||||
@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)
|
||||
click.echo("Displaying '{}'".format(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.')
|
||||
@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
|
||||
|
@ -122,14 +142,15 @@ def resize_cmd(images, width, height):
|
|||
"""
|
||||
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))
|
||||
click.echo("Resizing '{}' to {}x{}".format(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.')
|
||||
@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."""
|
||||
|
@ -139,7 +160,7 @@ def crop_cmd(images, border):
|
|||
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))
|
||||
click.echo("Cropping '{}' by {}px".format(image.filename, border))
|
||||
yield copy_filename(image.crop(box), image)
|
||||
else:
|
||||
yield image
|
||||
|
@ -149,96 +170,104 @@ def convert_rotation(ctx, param, value):
|
|||
if value is None:
|
||||
return
|
||||
value = value.lower()
|
||||
if value in ('90', 'r', 'right'):
|
||||
if value in ("90", "r", "right"):
|
||||
return (Image.ROTATE_90, 90)
|
||||
if value in ('180', '-180'):
|
||||
if value in ("180", "-180"):
|
||||
return (Image.ROTATE_180, 180)
|
||||
if value in ('-90', '270', 'l', 'left'):
|
||||
if value in ("-90", "270", "l", "left"):
|
||||
return (Image.ROTATE_270, 270)
|
||||
raise click.BadParameter('invalid rotation "%s"' % value)
|
||||
raise click.BadParameter("invalid rotation '{}'".format(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)
|
||||
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 '{}'".format(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]')
|
||||
@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))
|
||||
click.echo("Rotate '{}' by {}deg".format(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))
|
||||
click.echo("Flip '{}' {}".format(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.')
|
||||
@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))
|
||||
click.echo("Blurring '{}' by {}px".format(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.')
|
||||
@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):
|
||||
click.echo(
|
||||
"Smoothening '{}' {} time{}".format(
|
||||
image.filename, iterations, "s" if iterations != 1 else ""
|
||||
)
|
||||
)
|
||||
for _ in range(iterations):
|
||||
image = copy_filename(image.filter(ImageFilter.BLUR), image)
|
||||
yield image
|
||||
|
||||
|
||||
@cli.command('emboss')
|
||||
@cli.command("emboss")
|
||||
@processor
|
||||
def emboss_cmd(images):
|
||||
"""Embosses an image."""
|
||||
for image in images:
|
||||
click.echo('Embossing "%s"' % image.filename)
|
||||
click.echo("Embossing '{}'".format(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)
|
||||
@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))
|
||||
click.echo("Sharpen '{}' by {}".format(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.')
|
||||
@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
|
||||
|
@ -253,13 +282,12 @@ def paste_cmd(images, left, right):
|
|||
yield image
|
||||
return
|
||||
|
||||
click.echo('Paste "%s" on "%s"' %
|
||||
(to_paste.filename, image.filename))
|
||||
click.echo("Paste '{}' on '{}'".format(to_paste.filename, image.filename))
|
||||
mask = None
|
||||
if to_paste.mode == 'RGBA' or 'transparency' in to_paste.info:
|
||||
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
|
||||
image.filename += "+{}".format(to_paste.filename)
|
||||
yield image
|
||||
|
||||
for image in imageiter:
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-imagepipe',
|
||||
version='1.0',
|
||||
py_modules=['imagepipe'],
|
||||
name="click-example-imagepipe",
|
||||
version="1.0",
|
||||
py_modules=["imagepipe"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
'pillow',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click", "pillow"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
imagepipe=imagepipe:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -2,8 +2,8 @@ import click
|
|||
|
||||
|
||||
@click.command()
|
||||
@click.argument('input', type=click.File('rb'), nargs=-1)
|
||||
@click.argument('output', type=click.File('wb'))
|
||||
@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
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-inout',
|
||||
version='0.1',
|
||||
py_modules=['inout'],
|
||||
name="click-example-inout",
|
||||
version="0.1",
|
||||
py_modules=["inout"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
inout=inout:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -17,54 +17,56 @@ def ship():
|
|||
"""Manages ships."""
|
||||
|
||||
|
||||
@ship.command('new')
|
||||
@click.argument('name')
|
||||
@ship.command("new")
|
||||
@click.argument("name")
|
||||
def ship_new(name):
|
||||
"""Creates a new ship."""
|
||||
click.echo('Created ship %s' % name)
|
||||
click.echo("Created ship {}".format(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.')
|
||||
@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))
|
||||
click.echo("Moving ship {} to {},{} with speed {}".format(ship, x, y, speed))
|
||||
|
||||
|
||||
@ship.command('shoot')
|
||||
@click.argument('ship')
|
||||
@click.argument('x', type=float)
|
||||
@click.argument('y', type=float)
|
||||
@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))
|
||||
click.echo("Ship {} fires to {},{}".format(ship, x, y))
|
||||
|
||||
|
||||
@cli.group('mine')
|
||||
@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.')
|
||||
@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))
|
||||
click.echo("Set {} mine at {},{}".format(ty, x, y))
|
||||
|
||||
|
||||
@mine.command('remove')
|
||||
@click.argument('x', type=float)
|
||||
@click.argument('y', type=float)
|
||||
@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))
|
||||
click.echo("Removed mine at {},{}".format(x, y))
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-naval',
|
||||
version='2.0',
|
||||
py_modules=['naval'],
|
||||
name="click-example-naval",
|
||||
version="2.0",
|
||||
py_modules=["naval"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
naval=naval:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import os
|
||||
import sys
|
||||
import posixpath
|
||||
import sys
|
||||
|
||||
import click
|
||||
|
||||
|
||||
class Repo(object):
|
||||
|
||||
def __init__(self, home):
|
||||
self.home = home
|
||||
self.config = {}
|
||||
|
@ -15,23 +14,32 @@ class Repo(object):
|
|||
def set_config(self, key, value):
|
||||
self.config[key] = value
|
||||
if self.verbose:
|
||||
click.echo(' config[%s] = %s' % (key, value), file=sys.stderr)
|
||||
click.echo(" config[{}] = {}".format(key, value), file=sys.stderr)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Repo %r>' % self.home
|
||||
return "<Repo {}>".format(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.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
|
||||
|
@ -50,12 +58,16 @@ def cli(ctx, repo_home, config, verbose):
|
|||
|
||||
|
||||
@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.')
|
||||
@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.
|
||||
|
@ -65,12 +77,12 @@ def clone(repo, src, dest, shallow, rev):
|
|||
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)))
|
||||
dest = posixpath.split(src)[-1] or "."
|
||||
click.echo("Cloning repo {} to {}".format(src, os.path.abspath(dest)))
|
||||
repo.home = dest
|
||||
if shallow:
|
||||
click.echo('Making shallow checkout')
|
||||
click.echo('Checking out revision %s' % rev)
|
||||
click.echo("Making shallow checkout")
|
||||
click.echo("Checking out revision {}".format(rev))
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -81,33 +93,35 @@ def delete(repo):
|
|||
|
||||
This will throw away the current repository.
|
||||
"""
|
||||
click.echo('Destroying repo %s' % repo.home)
|
||||
click.echo('Deleted!')
|
||||
click.echo("Destroying repo {}".format(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.')
|
||||
@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.')
|
||||
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())
|
||||
@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.
|
||||
|
@ -119,33 +133,34 @@ def commit(repo, files, message):
|
|||
will be committed.
|
||||
"""
|
||||
if not message:
|
||||
marker = '# Files to be committed:'
|
||||
hint = ['', '', marker, '#']
|
||||
marker = "# Files to be committed:"
|
||||
hint = ["", "", marker, "#"]
|
||||
for file in files:
|
||||
hint.append('# U %s' % file)
|
||||
message = click.edit('\n'.join(hint))
|
||||
hint.append("# U {}".format(file))
|
||||
message = click.edit("\n".join(hint))
|
||||
if message is None:
|
||||
click.echo('Aborted!')
|
||||
click.echo("Aborted!")
|
||||
return
|
||||
msg = message.split(marker)[0].rstrip()
|
||||
if not msg:
|
||||
click.echo('Aborted! Empty commit message')
|
||||
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)
|
||||
msg = "\n".join(message)
|
||||
click.echo("Files to be committed: {}".format(files))
|
||||
click.echo("Commit message:\n{}".format(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())
|
||||
@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))
|
||||
click.echo("Copy from {} -> {}".format(fn, dst))
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-repo',
|
||||
version='0.1',
|
||||
py_modules=['repo'],
|
||||
name="click-example-repo",
|
||||
version="0.1",
|
||||
py_modules=["repo"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
repo=repo:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-termui',
|
||||
version='1.0',
|
||||
py_modules=['termui'],
|
||||
name="click-example-termui",
|
||||
version="1.0",
|
||||
py_modules=["termui"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
"click",
|
||||
# Colorama is only required for Windows.
|
||||
'colorama',
|
||||
"colorama",
|
||||
],
|
||||
entry_points='''
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
termui=termui:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
# coding: utf-8
|
||||
import click
|
||||
import math
|
||||
import time
|
||||
import random
|
||||
import time
|
||||
|
||||
try:
|
||||
range_type = xrange
|
||||
except NameError:
|
||||
range_type = range
|
||||
import click
|
||||
|
||||
|
||||
@click.group()
|
||||
|
@ -19,26 +15,30 @@ def cli():
|
|||
@cli.command()
|
||||
def colordemo():
|
||||
"""Demonstrates ANSI color support."""
|
||||
for color in 'red', 'green', 'blue':
|
||||
click.echo(click.style('I am colored %s' % color, fg=color))
|
||||
click.echo(click.style('I am background colored %s' % color, bg=color))
|
||||
for color in "red", "green", "blue":
|
||||
click.echo(click.style("I am colored {}".format(color), fg=color))
|
||||
click.echo(click.style("I am background colored {}".format(color), bg=color))
|
||||
|
||||
|
||||
@cli.command()
|
||||
def pager():
|
||||
"""Demonstrates using the pager."""
|
||||
lines = []
|
||||
for x in range_type(200):
|
||||
lines.append('%s. Hello World!' % click.style(str(x), fg='green'))
|
||||
click.echo_via_pager('\n'.join(lines))
|
||||
for x in range(200):
|
||||
lines.append("{}. Hello World!".format(click.style(str(x), fg="green")))
|
||||
click.echo_via_pager("\n".join(lines))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--count', default=8000, type=click.IntRange(1, 100000),
|
||||
help='The number of items to process.')
|
||||
@click.option(
|
||||
"--count",
|
||||
default=8000,
|
||||
type=click.IntRange(1, 100000),
|
||||
help="The number of items to process.",
|
||||
)
|
||||
def progress(count):
|
||||
"""Demonstrates the progress bar."""
|
||||
items = range_type(count)
|
||||
items = range(count)
|
||||
|
||||
def process_slowly(item):
|
||||
time.sleep(0.002 * random.random())
|
||||
|
@ -48,54 +48,68 @@ def progress(count):
|
|||
if random.random() > 0.3:
|
||||
yield item
|
||||
|
||||
with click.progressbar(items, label='Processing accounts',
|
||||
fill_char=click.style('#', fg='green')) as bar:
|
||||
with click.progressbar(
|
||||
items, label="Processing accounts", fill_char=click.style("#", fg="green")
|
||||
) as bar:
|
||||
for item in bar:
|
||||
process_slowly(item)
|
||||
|
||||
def show_item(item):
|
||||
if item is not None:
|
||||
return 'Item #%d' % item
|
||||
return "Item #{}".format(item)
|
||||
|
||||
with click.progressbar(filter(items), label='Committing transaction',
|
||||
fill_char=click.style('#', fg='yellow'),
|
||||
item_show_func=show_item) as bar:
|
||||
with click.progressbar(
|
||||
filter(items),
|
||||
label="Committing transaction",
|
||||
fill_char=click.style("#", fg="yellow"),
|
||||
item_show_func=show_item,
|
||||
) as bar:
|
||||
for item in bar:
|
||||
process_slowly(item)
|
||||
|
||||
with click.progressbar(length=count, label='Counting',
|
||||
bar_template='%(label)s %(bar)s | %(info)s',
|
||||
fill_char=click.style(u'█', fg='cyan'),
|
||||
empty_char=' ') as bar:
|
||||
with click.progressbar(
|
||||
length=count,
|
||||
label="Counting",
|
||||
bar_template="%(label)s %(bar)s | %(info)s",
|
||||
fill_char=click.style(u"█", fg="cyan"),
|
||||
empty_char=" ",
|
||||
) as bar:
|
||||
for item in bar:
|
||||
process_slowly(item)
|
||||
|
||||
with click.progressbar(length=count, width=0, show_percent=False,
|
||||
show_eta=False,
|
||||
fill_char=click.style('#', fg='magenta')) as bar:
|
||||
with click.progressbar(
|
||||
length=count,
|
||||
width=0,
|
||||
show_percent=False,
|
||||
show_eta=False,
|
||||
fill_char=click.style("#", fg="magenta"),
|
||||
) as bar:
|
||||
for item in bar:
|
||||
process_slowly(item)
|
||||
|
||||
# 'Non-linear progress bar'
|
||||
steps = [math.exp( x * 1. / 20) - 1 for x in range(20)]
|
||||
steps = [math.exp(x * 1.0 / 20) - 1 for x in range(20)]
|
||||
count = int(sum(steps))
|
||||
with click.progressbar(length=count, show_percent=False,
|
||||
label='Slowing progress bar',
|
||||
fill_char=click.style(u'█', fg='green')) as bar:
|
||||
with click.progressbar(
|
||||
length=count,
|
||||
show_percent=False,
|
||||
label="Slowing progress bar",
|
||||
fill_char=click.style(u"█", fg="green"),
|
||||
) as bar:
|
||||
for item in steps:
|
||||
time.sleep(item)
|
||||
bar.update(item)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('url')
|
||||
@click.argument("url")
|
||||
def open(url):
|
||||
"""Opens a file or URL In the default application."""
|
||||
click.launch(url)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('url')
|
||||
@click.argument("url")
|
||||
def locate(url):
|
||||
"""Opens a file or URL In the default application."""
|
||||
click.launch(url, locate=True)
|
||||
|
@ -104,16 +118,16 @@ def locate(url):
|
|||
@cli.command()
|
||||
def edit():
|
||||
"""Opens an editor with some text in it."""
|
||||
MARKER = '# Everything below is ignored\n'
|
||||
message = click.edit('\n\n' + MARKER)
|
||||
MARKER = "# Everything below is ignored\n"
|
||||
message = click.edit("\n\n{}".format(MARKER))
|
||||
if message is not None:
|
||||
msg = message.split(MARKER, 1)[0].rstrip('\n')
|
||||
msg = message.split(MARKER, 1)[0].rstrip("\n")
|
||||
if not msg:
|
||||
click.echo('Empty message!')
|
||||
click.echo("Empty message!")
|
||||
else:
|
||||
click.echo('Message:\n' + msg)
|
||||
click.echo("Message:\n{}".format(msg))
|
||||
else:
|
||||
click.echo('You did not enter anything!')
|
||||
click.echo("You did not enter anything!")
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -131,26 +145,26 @@ def pause():
|
|||
@cli.command()
|
||||
def menu():
|
||||
"""Shows a simple menu."""
|
||||
menu = 'main'
|
||||
menu = "main"
|
||||
while 1:
|
||||
if menu == 'main':
|
||||
click.echo('Main menu:')
|
||||
click.echo(' d: debug menu')
|
||||
click.echo(' q: quit')
|
||||
if menu == "main":
|
||||
click.echo("Main menu:")
|
||||
click.echo(" d: debug menu")
|
||||
click.echo(" q: quit")
|
||||
char = click.getchar()
|
||||
if char == 'd':
|
||||
menu = 'debug'
|
||||
elif char == 'q':
|
||||
menu = 'quit'
|
||||
if char == "d":
|
||||
menu = "debug"
|
||||
elif char == "q":
|
||||
menu = "quit"
|
||||
else:
|
||||
click.echo('Invalid input')
|
||||
elif menu == 'debug':
|
||||
click.echo('Debug menu')
|
||||
click.echo(' b: back')
|
||||
click.echo("Invalid input")
|
||||
elif menu == "debug":
|
||||
click.echo("Debug menu")
|
||||
click.echo(" b: back")
|
||||
char = click.getchar()
|
||||
if char == 'b':
|
||||
menu = 'main'
|
||||
if char == "b":
|
||||
menu = "main"
|
||||
else:
|
||||
click.echo('Invalid input')
|
||||
elif menu == 'quit':
|
||||
click.echo("Invalid input")
|
||||
elif menu == "quit":
|
||||
return
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='click-example-validation',
|
||||
version='1.0',
|
||||
py_modules=['validation'],
|
||||
name="click-example-validation",
|
||||
version="1.0",
|
||||
py_modules=["validation"],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
install_requires=["click"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
validation=validation:cli
|
||||
''',
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import click
|
||||
|
||||
try:
|
||||
from urllib import parse as urlparse
|
||||
except ImportError:
|
||||
|
@ -7,27 +8,32 @@ except ImportError:
|
|||
|
||||
def validate_count(ctx, param, value):
|
||||
if value < 0 or value % 2 != 0:
|
||||
raise click.BadParameter('Should be a positive, even integer.')
|
||||
raise click.BadParameter("Should be a positive, even integer.")
|
||||
return value
|
||||
|
||||
|
||||
class URL(click.ParamType):
|
||||
name = 'url'
|
||||
name = "url"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if not isinstance(value, tuple):
|
||||
value = urlparse.urlparse(value)
|
||||
if value.scheme not in ('http', 'https'):
|
||||
self.fail('invalid URL scheme (%s). Only HTTP URLs are '
|
||||
'allowed' % value.scheme, param, ctx)
|
||||
if value.scheme not in ("http", "https"):
|
||||
self.fail(
|
||||
"invalid URL scheme ({}). Only HTTP URLs are"
|
||||
" allowed".format(value.scheme),
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option('--count', default=2, callback=validate_count,
|
||||
help='A positive even number.')
|
||||
@click.option('--foo', help='A mysterious parameter.')
|
||||
@click.option('--url', help='A URL', type=URL())
|
||||
@click.option(
|
||||
"--count", default=2, callback=validate_count, help="A positive even number."
|
||||
)
|
||||
@click.option("--foo", help="A mysterious parameter.")
|
||||
@click.option("--url", help="A URL", type=URL())
|
||||
@click.version_option()
|
||||
def cli(count, foo, url):
|
||||
"""Validation.
|
||||
|
@ -36,9 +42,11 @@ def cli(count, foo, url):
|
|||
through callbacks, through a custom type as well as by validating
|
||||
manually in the function.
|
||||
"""
|
||||
if foo is not None and foo != 'wat':
|
||||
raise click.BadParameter('If a value is provided it needs to be the '
|
||||
'value "wat".', param_hint=['--foo'])
|
||||
click.echo('count: %s' % count)
|
||||
click.echo('foo: %s' % foo)
|
||||
click.echo('url: %s' % repr(url))
|
||||
if foo is not None and foo != "wat":
|
||||
raise click.BadParameter(
|
||||
'If a value is provided it needs to be the value "wat".',
|
||||
param_hint=["--foo"],
|
||||
)
|
||||
click.echo("count: {}".format(count))
|
||||
click.echo("foo: {}".format(foo))
|
||||
click.echo("url: {!r}".format(url))
|
||||
|
|
18
setup.cfg
18
setup.cfg
|
@ -6,18 +6,30 @@ universal = 1
|
|||
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
filterwarnings =
|
||||
error
|
||||
|
||||
[coverage:run]
|
||||
branch = True
|
||||
source =
|
||||
click
|
||||
src
|
||||
tests
|
||||
|
||||
[coverage:paths]
|
||||
source =
|
||||
click
|
||||
.tox/*/lib/python*/site-packages/click
|
||||
.tox/pypy/site-packages/click
|
||||
*/site-packages
|
||||
|
||||
[flake8]
|
||||
select = B, E, F, W, B9, ISC
|
||||
ignore =
|
||||
E203
|
||||
E501
|
||||
E722
|
||||
W503
|
||||
max-line-length = 80
|
||||
per-file-ignores =
|
||||
src/click/__init__.py: F401
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
|
|
24
setup.py
24
setup.py
|
@ -1,15 +1,17 @@
|
|||
import io
|
||||
import re
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
with io.open("README.rst", "rt", encoding="utf8") as f:
|
||||
readme = f.read()
|
||||
|
||||
with io.open("click/__init__.py", "rt", encoding="utf8") as f:
|
||||
version = re.search(r"__version__ = \'(.*?)\'", f.read()).group(1)
|
||||
with io.open("src/click/__init__.py", "rt", encoding="utf8") as f:
|
||||
version = re.search(r'__version__ = "(.*?)"', f.read()).group(1)
|
||||
|
||||
setup(
|
||||
name="Click",
|
||||
name="click",
|
||||
version=version,
|
||||
url="https://palletsprojects.com/p/click/",
|
||||
project_urls={
|
||||
|
@ -17,16 +19,15 @@ setup(
|
|||
"Code": "https://github.com/pallets/click",
|
||||
"Issue tracker": "https://github.com/pallets/click/issues",
|
||||
},
|
||||
license="BSD",
|
||||
author="Armin Ronacher",
|
||||
author_email="armin.ronacher@active-4.com",
|
||||
maintainer="Pallets Team",
|
||||
license="BSD-3-Clause",
|
||||
maintainer="Pallets",
|
||||
maintainer_email="contact@palletsprojects.com",
|
||||
description="Composable command line interface toolkit",
|
||||
long_description=readme,
|
||||
packages=["click"],
|
||||
packages=find_packages("src"),
|
||||
package_dir={"": "src"},
|
||||
include_package_data=True,
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
|
@ -34,11 +35,6 @@ setup(
|
|||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
Metadata-Version: 1.2
|
||||
Name: Click
|
||||
Version: 7.0
|
||||
Name: click
|
||||
Version: 7.1.2
|
||||
Summary: Composable command line interface toolkit
|
||||
Home-page: https://palletsprojects.com/p/click/
|
||||
Author: Armin Ronacher
|
||||
Author-email: armin.ronacher@active-4.com
|
||||
Maintainer: Pallets Team
|
||||
Maintainer: Pallets
|
||||
Maintainer-email: contact@palletsprojects.com
|
||||
License: BSD
|
||||
License: BSD-3-Clause
|
||||
Project-URL: Documentation, https://click.palletsprojects.com/
|
||||
Project-URL: Code, https://github.com/pallets/click
|
||||
Project-URL: Issue tracker, https://github.com/pallets/click/issues
|
||||
|
@ -37,9 +35,7 @@ Description: \$ click\_
|
|||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install click
|
||||
|
||||
Click supports Python 3.4 and newer, Python 2.7, and PyPy.
|
||||
$ pip install -U click
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
||||
|
||||
|
@ -47,26 +43,21 @@ Description: \$ click\_
|
|||
A Simple Example
|
||||
----------------
|
||||
|
||||
What does it look like? Here is an example of a simple Click program:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--count", default=1, help="Number of greetings.")
|
||||
@click.option("--name", prompt="Your name",
|
||||
help="The person to greet.")
|
||||
@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 _ in range(count):
|
||||
click.echo("Hello, %s!" % name)
|
||||
|
||||
click.echo(f"Hello, {name}!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
|
||||
And what it looks like when run:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python hello.py --count=3
|
||||
|
@ -90,18 +81,13 @@ Description: \$ click\_
|
|||
Links
|
||||
-----
|
||||
|
||||
* Website: https://palletsprojects.com/p/click/
|
||||
* Documentation: https://click.palletsprojects.com/
|
||||
* License: `BSD <https://github.com/pallets/click/blob/master/LICENSE.rst>`_
|
||||
* Releases: https://pypi.org/project/click/
|
||||
* Code: https://github.com/pallets/click
|
||||
* Issue tracker: https://github.com/pallets/click/issues
|
||||
* Test status:
|
||||
|
||||
* Linux, Mac: https://travis-ci.org/pallets/click
|
||||
* Windows: https://ci.appveyor.com/project/pallets/click
|
||||
|
||||
* Test coverage: https://codecov.io/gh/pallets/click
|
||||
- Website: https://palletsprojects.com/p/click/
|
||||
- Documentation: https://click.palletsprojects.com/
|
||||
- Releases: https://pypi.org/project/click/
|
||||
- Code: https://github.com/pallets/click
|
||||
- Issue tracker: https://github.com/pallets/click/issues
|
||||
- Test status: https://dev.azure.com/pallets/click/_build
|
||||
- Official chat: https://discord.gg/t6rrQZH
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
|
@ -110,10 +96,5 @@ Classifier: License :: OSI Approved :: BSD License
|
|||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
|
|
@ -1,33 +1,11 @@
|
|||
CHANGES.rst
|
||||
CONTRIBUTING.rst
|
||||
LICENSE.rst
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
setup.cfg
|
||||
setup.py
|
||||
tox.ini
|
||||
Click.egg-info/PKG-INFO
|
||||
Click.egg-info/SOURCES.txt
|
||||
Click.egg-info/dependency_links.txt
|
||||
Click.egg-info/top_level.txt
|
||||
artwork/logo.svg
|
||||
click/__init__.py
|
||||
click/_bashcomplete.py
|
||||
click/_compat.py
|
||||
click/_termui_impl.py
|
||||
click/_textwrap.py
|
||||
click/_unicodefun.py
|
||||
click/_winconsole.py
|
||||
click/core.py
|
||||
click/decorators.py
|
||||
click/exceptions.py
|
||||
click/formatting.py
|
||||
click/globals.py
|
||||
click/parser.py
|
||||
click/termui.py
|
||||
click/testing.py
|
||||
click/types.py
|
||||
click/utils.py
|
||||
docs/Makefile
|
||||
docs/advanced.rst
|
||||
docs/api.rst
|
||||
|
@ -97,6 +75,27 @@ examples/termui/termui.py
|
|||
examples/validation/README
|
||||
examples/validation/setup.py
|
||||
examples/validation/validation.py
|
||||
src/click/__init__.py
|
||||
src/click/_bashcomplete.py
|
||||
src/click/_compat.py
|
||||
src/click/_termui_impl.py
|
||||
src/click/_textwrap.py
|
||||
src/click/_unicodefun.py
|
||||
src/click/_winconsole.py
|
||||
src/click/core.py
|
||||
src/click/decorators.py
|
||||
src/click/exceptions.py
|
||||
src/click/formatting.py
|
||||
src/click/globals.py
|
||||
src/click/parser.py
|
||||
src/click/termui.py
|
||||
src/click/testing.py
|
||||
src/click/types.py
|
||||
src/click/utils.py
|
||||
src/click.egg-info/PKG-INFO
|
||||
src/click.egg-info/SOURCES.txt
|
||||
src/click.egg-info/dependency_links.txt
|
||||
src/click.egg-info/top_level.txt
|
||||
tests/conftest.py
|
||||
tests/test_arguments.py
|
||||
tests/test_bashcomplete.py
|
79
src/click/__init__.py
Normal file
79
src/click/__init__.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
Click is a simple Python module inspired by the stdlib 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.
|
||||
"""
|
||||
from .core import Argument
|
||||
from .core import BaseCommand
|
||||
from .core import Command
|
||||
from .core import CommandCollection
|
||||
from .core import Context
|
||||
from .core import Group
|
||||
from .core import MultiCommand
|
||||
from .core import Option
|
||||
from .core import Parameter
|
||||
from .decorators import argument
|
||||
from .decorators import command
|
||||
from .decorators import confirmation_option
|
||||
from .decorators import group
|
||||
from .decorators import help_option
|
||||
from .decorators import make_pass_decorator
|
||||
from .decorators import option
|
||||
from .decorators import pass_context
|
||||
from .decorators import pass_obj
|
||||
from .decorators import password_option
|
||||
from .decorators import version_option
|
||||
from .exceptions import Abort
|
||||
from .exceptions import BadArgumentUsage
|
||||
from .exceptions import BadOptionUsage
|
||||
from .exceptions import BadParameter
|
||||
from .exceptions import ClickException
|
||||
from .exceptions import FileError
|
||||
from .exceptions import MissingParameter
|
||||
from .exceptions import NoSuchOption
|
||||
from .exceptions import UsageError
|
||||
from .formatting import HelpFormatter
|
||||
from .formatting import wrap_text
|
||||
from .globals import get_current_context
|
||||
from .parser import OptionParser
|
||||
from .termui import clear
|
||||
from .termui import confirm
|
||||
from .termui import echo_via_pager
|
||||
from .termui import edit
|
||||
from .termui import get_terminal_size
|
||||
from .termui import getchar
|
||||
from .termui import launch
|
||||
from .termui import pause
|
||||
from .termui import progressbar
|
||||
from .termui import prompt
|
||||
from .termui import secho
|
||||
from .termui import style
|
||||
from .termui import unstyle
|
||||
from .types import BOOL
|
||||
from .types import Choice
|
||||
from .types import DateTime
|
||||
from .types import File
|
||||
from .types import FLOAT
|
||||
from .types import FloatRange
|
||||
from .types import INT
|
||||
from .types import IntRange
|
||||
from .types import ParamType
|
||||
from .types import Path
|
||||
from .types import STRING
|
||||
from .types import Tuple
|
||||
from .types import UNPROCESSED
|
||||
from .types import UUID
|
||||
from .utils import echo
|
||||
from .utils import format_filename
|
||||
from .utils import get_app_dir
|
||||
from .utils import get_binary_stream
|
||||
from .utils import get_os_args
|
||||
from .utils import get_text_stream
|
||||
from .utils import open_file
|
||||
|
||||
# Controls if click should emit the warning about the use of unicode
|
||||
# literals.
|
||||
disable_unicode_literals_warning = False
|
||||
|
||||
__version__ = "7.1.2"
|
|
@ -2,20 +2,22 @@ import copy
|
|||
import os
|
||||
import re
|
||||
|
||||
from .utils import echo
|
||||
from .core import Argument
|
||||
from .core import MultiCommand
|
||||
from .core import Option
|
||||
from .parser import split_arg_string
|
||||
from .core import MultiCommand, Option, Argument
|
||||
from .types import Choice
|
||||
from .utils import echo
|
||||
|
||||
try:
|
||||
from collections import abc
|
||||
except ImportError:
|
||||
import collections as abc
|
||||
|
||||
WORDBREAK = '='
|
||||
WORDBREAK = "="
|
||||
|
||||
# Note, only BASH version 4.4 and later have the nosort option.
|
||||
COMPLETION_SCRIPT_BASH = '''
|
||||
COMPLETION_SCRIPT_BASH = """
|
||||
%(complete_func)s() {
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
|
@ -28,7 +30,8 @@ COMPLETION_SCRIPT_BASH = '''
|
|||
local COMPLETION_OPTIONS=""
|
||||
local BASH_VERSION_ARR=(${BASH_VERSION//./ })
|
||||
# Only BASH version 4.4 and later have the nosort option.
|
||||
if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then
|
||||
if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] \
|
||||
&& [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then
|
||||
COMPLETION_OPTIONS="-o nosort"
|
||||
fi
|
||||
|
||||
|
@ -36,13 +39,17 @@ COMPLETION_SCRIPT_BASH = '''
|
|||
}
|
||||
|
||||
%(complete_func)setup
|
||||
'''
|
||||
"""
|
||||
|
||||
COMPLETION_SCRIPT_ZSH = """
|
||||
#compdef %(script_names)s
|
||||
|
||||
COMPLETION_SCRIPT_ZSH = '''
|
||||
%(complete_func)s() {
|
||||
local -a completions
|
||||
local -a completions_with_descriptions
|
||||
local -a response
|
||||
(( ! $+commands[%(script_names)s] )) && return 1
|
||||
|
||||
response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\
|
||||
COMP_CWORD=$((CURRENT-1)) \\
|
||||
%(autocomplete_var)s=\"complete_zsh\" \\
|
||||
|
@ -57,34 +64,51 @@ COMPLETION_SCRIPT_ZSH = '''
|
|||
done
|
||||
|
||||
if [ -n "$completions_with_descriptions" ]; then
|
||||
_describe -V unsorted completions_with_descriptions -U -Q
|
||||
_describe -V unsorted completions_with_descriptions -U
|
||||
fi
|
||||
|
||||
if [ -n "$completions" ]; then
|
||||
compadd -U -V unsorted -Q -a completions
|
||||
compadd -U -V unsorted -a completions
|
||||
fi
|
||||
compstate[insert]="automenu"
|
||||
}
|
||||
|
||||
compdef %(complete_func)s %(script_names)s
|
||||
'''
|
||||
"""
|
||||
|
||||
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
||||
COMPLETION_SCRIPT_FISH = (
|
||||
"complete --no-files --command %(script_names)s --arguments"
|
||||
' "(env %(autocomplete_var)s=complete_fish'
|
||||
" COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t)"
|
||||
' %(script_names)s)"'
|
||||
)
|
||||
|
||||
_completion_scripts = {
|
||||
"bash": COMPLETION_SCRIPT_BASH,
|
||||
"zsh": COMPLETION_SCRIPT_ZSH,
|
||||
"fish": COMPLETION_SCRIPT_FISH,
|
||||
}
|
||||
|
||||
_invalid_ident_char_re = re.compile(r"[^a-zA-Z0-9_]")
|
||||
|
||||
|
||||
def get_completion_script(prog_name, complete_var, shell):
|
||||
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
|
||||
script = COMPLETION_SCRIPT_ZSH if shell == 'zsh' else COMPLETION_SCRIPT_BASH
|
||||
return (script % {
|
||||
'complete_func': '_%s_completion' % cf_name,
|
||||
'script_names': prog_name,
|
||||
'autocomplete_var': complete_var,
|
||||
}).strip() + ';'
|
||||
cf_name = _invalid_ident_char_re.sub("", prog_name.replace("-", "_"))
|
||||
script = _completion_scripts.get(shell, COMPLETION_SCRIPT_BASH)
|
||||
return (
|
||||
script
|
||||
% {
|
||||
"complete_func": "_{}_completion".format(cf_name),
|
||||
"script_names": prog_name,
|
||||
"autocomplete_var": complete_var,
|
||||
}
|
||||
).strip() + ";"
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
"""
|
||||
Parse into a hierarchy of contexts. Contexts are connected through the parent variable.
|
||||
"""Parse into a hierarchy of contexts. Contexts are connected
|
||||
through the parent variable.
|
||||
|
||||
:param cli: command definition
|
||||
:param prog_name: the program that is running
|
||||
:param args: full list of args
|
||||
|
@ -98,8 +122,9 @@ def resolve_ctx(cli, prog_name, args):
|
|||
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||
if cmd is None:
|
||||
return ctx
|
||||
ctx = cmd.make_context(cmd_name, args, parent=ctx,
|
||||
resilient_parsing=True)
|
||||
ctx = cmd.make_context(
|
||||
cmd_name, args, parent=ctx, resilient_parsing=True
|
||||
)
|
||||
args = ctx.protected_args + ctx.args
|
||||
else:
|
||||
# Walk chained subcommand contexts saving the last one.
|
||||
|
@ -107,10 +132,14 @@ def resolve_ctx(cli, prog_name, args):
|
|||
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||
if cmd is None:
|
||||
return ctx
|
||||
sub_ctx = cmd.make_context(cmd_name, args, parent=ctx,
|
||||
allow_extra_args=True,
|
||||
allow_interspersed_args=False,
|
||||
resilient_parsing=True)
|
||||
sub_ctx = cmd.make_context(
|
||||
cmd_name,
|
||||
args,
|
||||
parent=ctx,
|
||||
allow_extra_args=True,
|
||||
allow_interspersed_args=False,
|
||||
resilient_parsing=True,
|
||||
)
|
||||
args = sub_ctx.args
|
||||
ctx = sub_ctx
|
||||
args = sub_ctx.protected_args + sub_ctx.args
|
||||
|
@ -122,25 +151,29 @@ def resolve_ctx(cli, prog_name, args):
|
|||
def start_of_option(param_str):
|
||||
"""
|
||||
:param param_str: param_str to check
|
||||
:return: whether or not this is the start of an option declaration (i.e. starts "-" or "--")
|
||||
:return: whether or not this is the start of an option declaration
|
||||
(i.e. starts "-" or "--")
|
||||
"""
|
||||
return param_str and param_str[:1] == '-'
|
||||
return param_str and param_str[:1] == "-"
|
||||
|
||||
|
||||
def is_incomplete_option(all_args, cmd_param):
|
||||
"""
|
||||
:param all_args: the full original list of args supplied
|
||||
:param cmd_param: the current command paramter
|
||||
:return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and
|
||||
corresponds to this cmd_param. In other words whether this cmd_param option can still accept
|
||||
values
|
||||
:return: whether or not the last option declaration (i.e. starts
|
||||
"-" or "--") is incomplete and corresponds to this cmd_param. In
|
||||
other words whether this cmd_param option can still accept
|
||||
values
|
||||
"""
|
||||
if not isinstance(cmd_param, Option):
|
||||
return False
|
||||
if cmd_param.is_flag:
|
||||
return False
|
||||
last_option = None
|
||||
for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])):
|
||||
for index, arg_str in enumerate(
|
||||
reversed([arg for arg in all_args if arg != WORDBREAK])
|
||||
):
|
||||
if index + 1 > cmd_param.nargs:
|
||||
break
|
||||
if start_of_option(arg_str):
|
||||
|
@ -151,10 +184,12 @@ def is_incomplete_option(all_args, cmd_param):
|
|||
|
||||
def is_incomplete_argument(current_params, cmd_param):
|
||||
"""
|
||||
:param current_params: the current params and values for this argument as already entered
|
||||
:param current_params: the current params and values for this
|
||||
argument as already entered
|
||||
:param cmd_param: the current command parameter
|
||||
:return: whether or not the last argument is incomplete and corresponds to this cmd_param. In
|
||||
other words whether or not the this cmd_param argument can still accept values
|
||||
:return: whether or not the last argument is incomplete and
|
||||
corresponds to this cmd_param. In other words whether or not the
|
||||
this cmd_param argument can still accept values
|
||||
"""
|
||||
if not isinstance(cmd_param, Argument):
|
||||
return False
|
||||
|
@ -163,8 +198,11 @@ def is_incomplete_argument(current_params, cmd_param):
|
|||
return True
|
||||
if cmd_param.nargs == -1:
|
||||
return True
|
||||
if isinstance(current_param_values, abc.Iterable) \
|
||||
and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs:
|
||||
if (
|
||||
isinstance(current_param_values, abc.Iterable)
|
||||
and cmd_param.nargs > 1
|
||||
and len(current_param_values) < cmd_param.nargs
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -180,14 +218,16 @@ def get_user_autocompletions(ctx, args, incomplete, cmd_param):
|
|||
results = []
|
||||
if isinstance(cmd_param.type, Choice):
|
||||
# Choices don't support descriptions.
|
||||
results = [(c, None)
|
||||
for c in cmd_param.type.choices if str(c).startswith(incomplete)]
|
||||
results = [
|
||||
(c, None) for c in cmd_param.type.choices if str(c).startswith(incomplete)
|
||||
]
|
||||
elif cmd_param.autocompletion is not None:
|
||||
dynamic_completions = cmd_param.autocompletion(ctx=ctx,
|
||||
args=args,
|
||||
incomplete=incomplete)
|
||||
results = [c if isinstance(c, tuple) else (c, None)
|
||||
for c in dynamic_completions]
|
||||
dynamic_completions = cmd_param.autocompletion(
|
||||
ctx=ctx, args=args, incomplete=incomplete
|
||||
)
|
||||
results = [
|
||||
c if isinstance(c, tuple) else (c, None) for c in dynamic_completions
|
||||
]
|
||||
return results
|
||||
|
||||
|
||||
|
@ -208,15 +248,25 @@ def add_subcommand_completions(ctx, incomplete, completions_out):
|
|||
# Add subcommand completions.
|
||||
if isinstance(ctx.command, MultiCommand):
|
||||
completions_out.extend(
|
||||
[(c.name, c.get_short_help_str()) for c in get_visible_commands_starting_with(ctx, incomplete)])
|
||||
[
|
||||
(c.name, c.get_short_help_str())
|
||||
for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||
]
|
||||
)
|
||||
|
||||
# Walk up the context list and add any other completion possibilities from chained commands
|
||||
# Walk up the context list and add any other completion
|
||||
# possibilities from chained commands
|
||||
while ctx.parent is not None:
|
||||
ctx = ctx.parent
|
||||
if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
|
||||
remaining_commands = [c for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||
if c.name not in ctx.protected_args]
|
||||
completions_out.extend([(c.name, c.get_short_help_str()) for c in remaining_commands])
|
||||
remaining_commands = [
|
||||
c
|
||||
for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||
if c.name not in ctx.protected_args
|
||||
]
|
||||
completions_out.extend(
|
||||
[(c.name, c.get_short_help_str()) for c in remaining_commands]
|
||||
)
|
||||
|
||||
|
||||
def get_choices(cli, prog_name, args, incomplete):
|
||||
|
@ -233,23 +283,30 @@ def get_choices(cli, prog_name, args, incomplete):
|
|||
if ctx is None:
|
||||
return []
|
||||
|
||||
# In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
|
||||
# without the '='
|
||||
has_double_dash = "--" in all_args
|
||||
|
||||
# In newer versions of bash long opts with '='s are partitioned, but
|
||||
# it's easier to parse without the '='
|
||||
if start_of_option(incomplete) and WORDBREAK in incomplete:
|
||||
partition_incomplete = incomplete.partition(WORDBREAK)
|
||||
all_args.append(partition_incomplete[0])
|
||||
incomplete = partition_incomplete[2]
|
||||
elif incomplete == WORDBREAK:
|
||||
incomplete = ''
|
||||
incomplete = ""
|
||||
|
||||
completions = []
|
||||
if start_of_option(incomplete):
|
||||
if not has_double_dash and start_of_option(incomplete):
|
||||
# completions for partial options
|
||||
for param in ctx.command.params:
|
||||
if isinstance(param, Option) and not param.hidden:
|
||||
param_opts = [param_opt for param_opt in param.opts +
|
||||
param.secondary_opts if param_opt not in all_args or param.multiple]
|
||||
completions.extend([(o, param.help) for o in param_opts if o.startswith(incomplete)])
|
||||
param_opts = [
|
||||
param_opt
|
||||
for param_opt in param.opts + param.secondary_opts
|
||||
if param_opt not in all_args or param.multiple
|
||||
]
|
||||
completions.extend(
|
||||
[(o, param.help) for o in param_opts if o.startswith(incomplete)]
|
||||
)
|
||||
return completions
|
||||
# completion for option values from user supplied values
|
||||
for param in ctx.command.params:
|
||||
|
@ -266,28 +323,53 @@ def get_choices(cli, prog_name, args, incomplete):
|
|||
|
||||
|
||||
def do_complete(cli, prog_name, include_descriptions):
|
||||
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
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 = ''
|
||||
incomplete = ""
|
||||
|
||||
for item in get_choices(cli, prog_name, args, incomplete):
|
||||
echo(item[0])
|
||||
if include_descriptions:
|
||||
# ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present.
|
||||
echo(item[1] if item[1] else '_')
|
||||
# ZSH has trouble dealing with empty array parameters when
|
||||
# returned from commands, use '_' to indicate no description
|
||||
# is present.
|
||||
echo(item[1] if item[1] else "_")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_complete_fish(cli, prog_name):
|
||||
cwords = split_arg_string(os.environ["COMP_WORDS"])
|
||||
incomplete = os.environ["COMP_CWORD"]
|
||||
args = cwords[1:]
|
||||
|
||||
for item in get_choices(cli, prog_name, args, incomplete):
|
||||
if item[1]:
|
||||
echo("{arg}\t{desc}".format(arg=item[0], desc=item[1]))
|
||||
else:
|
||||
echo(item[0])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||
if complete_instr.startswith('source'):
|
||||
shell = 'zsh' if complete_instr == 'source_zsh' else 'bash'
|
||||
if "_" in complete_instr:
|
||||
command, shell = complete_instr.split("_", 1)
|
||||
else:
|
||||
command = complete_instr
|
||||
shell = "bash"
|
||||
|
||||
if command == "source":
|
||||
echo(get_completion_script(prog_name, complete_var, shell))
|
||||
return True
|
||||
elif complete_instr == 'complete' or complete_instr == 'complete_zsh':
|
||||
return do_complete(cli, prog_name, complete_instr == 'complete_zsh')
|
||||
elif command == "complete":
|
||||
if shell == "fish":
|
||||
return do_complete_fish(cli, prog_name)
|
||||
elif shell in {"bash", "zsh"}:
|
||||
return do_complete(cli, prog_name, shell == "zsh")
|
||||
|
||||
return False
|
|
@ -1,67 +1,80 @@
|
|||
import re
|
||||
# flake8: noqa
|
||||
import codecs
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import codecs
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
CYGWIN = sys.platform.startswith('cygwin')
|
||||
CYGWIN = sys.platform.startswith("cygwin")
|
||||
MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version)
|
||||
# Determine local App Engine environment, per Google's own suggestion
|
||||
APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and
|
||||
'Development/' in os.environ['SERVER_SOFTWARE'])
|
||||
WIN = sys.platform.startswith('win') and not APP_ENGINE
|
||||
APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get(
|
||||
"SERVER_SOFTWARE", ""
|
||||
)
|
||||
WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2
|
||||
DEFAULT_COLUMNS = 80
|
||||
|
||||
|
||||
_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
||||
def _make_text_stream(stream, encoding, errors,
|
||||
force_readable=False, force_writable=False):
|
||||
def _make_text_stream(
|
||||
stream, encoding, errors, force_readable=False, force_writable=False
|
||||
):
|
||||
if encoding is None:
|
||||
encoding = get_best_encoding(stream)
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
||||
line_buffering=True,
|
||||
force_readable=force_readable,
|
||||
force_writable=force_writable)
|
||||
errors = "replace"
|
||||
return _NonClosingTextIOWrapper(
|
||||
stream,
|
||||
encoding,
|
||||
errors,
|
||||
line_buffering=True,
|
||||
force_readable=force_readable,
|
||||
force_writable=force_writable,
|
||||
)
|
||||
|
||||
|
||||
def is_ascii_encoding(encoding):
|
||||
"""Checks if a given encoding is ascii."""
|
||||
try:
|
||||
return codecs.lookup(encoding).name == 'ascii'
|
||||
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()
|
||||
rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
|
||||
if is_ascii_encoding(rv):
|
||||
return 'utf-8'
|
||||
return "utf-8"
|
||||
return rv
|
||||
|
||||
|
||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||
|
||||
def __init__(self, stream, encoding, errors,
|
||||
force_readable=False, force_writable=False, **extra):
|
||||
self._stream = stream = _FixupStream(stream, force_readable,
|
||||
force_writable)
|
||||
def __init__(
|
||||
self,
|
||||
stream,
|
||||
encoding,
|
||||
errors,
|
||||
force_readable=False,
|
||||
force_writable=False,
|
||||
**extra
|
||||
):
|
||||
self._stream = stream = _FixupStream(stream, force_readable, force_writable)
|
||||
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
||||
|
||||
# The io module is a place where the Python 3 text behavior
|
||||
# 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:
|
||||
|
@ -105,7 +118,7 @@ class _FixupStream(object):
|
|||
return getattr(self._stream, name)
|
||||
|
||||
def read1(self, size):
|
||||
f = getattr(self._stream, 'read1', None)
|
||||
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
|
||||
|
@ -118,7 +131,7 @@ class _FixupStream(object):
|
|||
def readable(self):
|
||||
if self._force_readable:
|
||||
return True
|
||||
x = getattr(self._stream, 'readable', None)
|
||||
x = getattr(self._stream, "readable", None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
|
@ -130,20 +143,20 @@ class _FixupStream(object):
|
|||
def writable(self):
|
||||
if self._force_writable:
|
||||
return True
|
||||
x = getattr(self._stream, 'writable', None)
|
||||
x = getattr(self._stream, "writable", None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.write('')
|
||||
self._stream.write("")
|
||||
except Exception:
|
||||
try:
|
||||
self._stream.write(b'')
|
||||
self._stream.write(b"")
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def seekable(self):
|
||||
x = getattr(self._stream, 'seekable', None)
|
||||
x = getattr(self._stream, "seekable", None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
|
@ -155,7 +168,6 @@ class _FixupStream(object):
|
|||
|
||||
if PY2:
|
||||
text_type = unicode
|
||||
bytes = str
|
||||
raw_input = raw_input
|
||||
string_types = (str, unicode)
|
||||
int_types = (int, long)
|
||||
|
@ -165,7 +177,7 @@ if PY2:
|
|||
def is_bytes(x):
|
||||
return isinstance(x, (buffer, bytearray))
|
||||
|
||||
_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
|
||||
_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
|
||||
|
@ -193,6 +205,7 @@ if PY2:
|
|||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
|
||||
def set_binary_mode(f):
|
||||
try:
|
||||
fileno = f.fileno()
|
||||
|
@ -207,6 +220,7 @@ if PY2:
|
|||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
|
||||
def set_binary_mode(f):
|
||||
try:
|
||||
fileno = f.fileno()
|
||||
|
@ -224,42 +238,42 @@ if PY2:
|
|||
return set_binary_mode(sys.stdin)
|
||||
|
||||
def get_binary_stdout():
|
||||
_wrap_std_stream('stdout')
|
||||
_wrap_std_stream("stdout")
|
||||
return set_binary_mode(sys.stdout)
|
||||
|
||||
def get_binary_stderr():
|
||||
_wrap_std_stream('stderr')
|
||||
_wrap_std_stream("stderr")
|
||||
return set_binary_mode(sys.stderr)
|
||||
|
||||
def get_text_stdin(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _make_text_stream(sys.stdin, encoding, errors,
|
||||
force_readable=True)
|
||||
return _make_text_stream(sys.stdin, encoding, errors, force_readable=True)
|
||||
|
||||
def get_text_stdout(encoding=None, errors=None):
|
||||
_wrap_std_stream('stdout')
|
||||
_wrap_std_stream("stdout")
|
||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _make_text_stream(sys.stdout, encoding, errors,
|
||||
force_writable=True)
|
||||
return _make_text_stream(sys.stdout, encoding, errors, force_writable=True)
|
||||
|
||||
def get_text_stderr(encoding=None, errors=None):
|
||||
_wrap_std_stream('stderr')
|
||||
_wrap_std_stream("stderr")
|
||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _make_text_stream(sys.stderr, encoding, errors,
|
||||
force_writable=True)
|
||||
return _make_text_stream(sys.stderr, encoding, errors, force_writable=True)
|
||||
|
||||
def filename_to_ui(value):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(get_filesystem_encoding(), 'replace')
|
||||
value = value.decode(get_filesystem_encoding(), "replace")
|
||||
return value
|
||||
|
||||
|
||||
else:
|
||||
import io
|
||||
|
||||
text_type = str
|
||||
raw_input = input
|
||||
string_types = (str,)
|
||||
|
@ -281,10 +295,10 @@ else:
|
|||
|
||||
def _is_binary_writer(stream, default=False):
|
||||
try:
|
||||
stream.write(b'')
|
||||
stream.write(b"")
|
||||
except Exception:
|
||||
try:
|
||||
stream.write('')
|
||||
stream.write("")
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
|
@ -299,7 +313,7 @@ else:
|
|||
if _is_binary_reader(stream, False):
|
||||
return stream
|
||||
|
||||
buf = getattr(stream, 'buffer', None)
|
||||
buf = getattr(stream, "buffer", None)
|
||||
|
||||
# Same situation here; this time we assume that the buffer is
|
||||
# actually binary in case it's closed.
|
||||
|
@ -314,7 +328,7 @@ else:
|
|||
if _is_binary_writer(stream, False):
|
||||
return stream
|
||||
|
||||
buf = getattr(stream, 'buffer', None)
|
||||
buf = getattr(stream, "buffer", None)
|
||||
|
||||
# Same situation here; this time we assume that the buffer is
|
||||
# actually binary in case it's closed.
|
||||
|
@ -327,136 +341,142 @@ else:
|
|||
# to ASCII. This appears to happen in certain unittest
|
||||
# environments. It's not quite clear what the correct behavior is
|
||||
# but this at least will force Click to recover somehow.
|
||||
return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii')
|
||||
return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
|
||||
|
||||
def _is_compat_stream_attr(stream, attr, value):
|
||||
"""A stream attribute is compatible if it is equal to the
|
||||
desired value or the desired value is unset and the attribute
|
||||
has a value.
|
||||
"""
|
||||
stream_value = getattr(stream, attr, None)
|
||||
return stream_value == value or (value is None and stream_value is not None)
|
||||
|
||||
def _is_compatible_text_stream(stream, encoding, errors):
|
||||
stream_encoding = getattr(stream, 'encoding', None)
|
||||
stream_errors = getattr(stream, 'errors', None)
|
||||
"""Check if a stream's encoding and errors attributes are
|
||||
compatible with the desired values.
|
||||
"""
|
||||
return _is_compat_stream_attr(
|
||||
stream, "encoding", encoding
|
||||
) and _is_compat_stream_attr(stream, "errors", errors)
|
||||
|
||||
# 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,
|
||||
force_readable=False):
|
||||
if _is_binary_reader(text_reader, False):
|
||||
binary_reader = text_reader
|
||||
def _force_correct_text_stream(
|
||||
text_stream,
|
||||
encoding,
|
||||
errors,
|
||||
is_binary,
|
||||
find_binary,
|
||||
force_readable=False,
|
||||
force_writable=False,
|
||||
):
|
||||
if is_binary(text_stream, False):
|
||||
binary_reader = text_stream
|
||||
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 the stream looks compatible, and won't default to a
|
||||
# misconfigured ascii encoding, return it as-is.
|
||||
if _is_compatible_text_stream(text_stream, encoding, errors) and not (
|
||||
encoding is None and _stream_is_misconfigured(text_stream)
|
||||
):
|
||||
return text_stream
|
||||
|
||||
if _is_compatible_text_stream(text_reader, encoding, errors):
|
||||
return text_reader
|
||||
# Otherwise, get the underlying binary reader.
|
||||
binary_reader = find_binary(text_stream)
|
||||
|
||||
# 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 that's not possible, silently use the original reader
|
||||
# and get mojibake instead of exceptions.
|
||||
if binary_reader is None:
|
||||
return text_reader
|
||||
return text_stream
|
||||
|
||||
# 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.
|
||||
# Default errors to replace instead of strict in order to get
|
||||
# something that works.
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _make_text_stream(binary_reader, encoding, errors,
|
||||
force_readable=force_readable)
|
||||
errors = "replace"
|
||||
|
||||
def _force_correct_text_writer(text_writer, encoding, errors,
|
||||
force_writable=False):
|
||||
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
|
||||
# Wrap the binary stream in a text stream with the correct
|
||||
# encoding parameters.
|
||||
return _make_text_stream(
|
||||
binary_reader,
|
||||
encoding,
|
||||
errors,
|
||||
force_readable=force_readable,
|
||||
force_writable=force_writable,
|
||||
)
|
||||
|
||||
if _is_compatible_text_stream(text_writer, encoding, errors):
|
||||
return text_writer
|
||||
def _force_correct_text_reader(text_reader, encoding, errors, force_readable=False):
|
||||
return _force_correct_text_stream(
|
||||
text_reader,
|
||||
encoding,
|
||||
errors,
|
||||
_is_binary_reader,
|
||||
_find_binary_reader,
|
||||
force_readable=force_readable,
|
||||
)
|
||||
|
||||
# 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,
|
||||
force_writable=force_writable)
|
||||
def _force_correct_text_writer(text_writer, encoding, errors, force_writable=False):
|
||||
return _force_correct_text_stream(
|
||||
text_writer,
|
||||
encoding,
|
||||
errors,
|
||||
_is_binary_writer,
|
||||
_find_binary_writer,
|
||||
force_writable=force_writable,
|
||||
)
|
||||
|
||||
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.')
|
||||
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.')
|
||||
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.')
|
||||
raise RuntimeError(
|
||||
"Was not able to determine binary stream for sys.stderr."
|
||||
)
|
||||
return writer
|
||||
|
||||
def get_text_stdin(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _force_correct_text_reader(sys.stdin, encoding, errors,
|
||||
force_readable=True)
|
||||
return _force_correct_text_reader(
|
||||
sys.stdin, encoding, errors, force_readable=True
|
||||
)
|
||||
|
||||
def get_text_stdout(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _force_correct_text_writer(sys.stdout, encoding, errors,
|
||||
force_writable=True)
|
||||
return _force_correct_text_writer(
|
||||
sys.stdout, encoding, errors, force_writable=True
|
||||
)
|
||||
|
||||
def get_text_stderr(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _force_correct_text_writer(sys.stderr, encoding, errors,
|
||||
force_writable=True)
|
||||
return _force_correct_text_writer(
|
||||
sys.stderr, encoding, errors, force_writable=True
|
||||
)
|
||||
|
||||
def filename_to_ui(value):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(get_filesystem_encoding(), 'replace')
|
||||
value = value.decode(get_filesystem_encoding(), "replace")
|
||||
else:
|
||||
value = value.encode('utf-8', 'surrogateescape') \
|
||||
.decode('utf-8', 'replace')
|
||||
value = value.encode("utf-8", "surrogateescape").decode("utf-8", "replace")
|
||||
return value
|
||||
|
||||
|
||||
def get_streerror(e, default=None):
|
||||
if hasattr(e, 'strerror'):
|
||||
if hasattr(e, "strerror"):
|
||||
msg = e.strerror
|
||||
else:
|
||||
if default is not None:
|
||||
|
@ -464,60 +484,107 @@ def get_streerror(e, default=None):
|
|||
else:
|
||||
msg = str(e)
|
||||
if isinstance(msg, bytes):
|
||||
msg = msg.decode('utf-8', 'replace')
|
||||
msg = msg.decode("utf-8", "replace")
|
||||
return msg
|
||||
|
||||
|
||||
def open_stream(filename, mode='r', encoding=None, errors='strict',
|
||||
atomic=False):
|
||||
def _wrap_io_open(file, mode, encoding, errors):
|
||||
"""On Python 2, :func:`io.open` returns a text file wrapper that
|
||||
requires passing ``unicode`` to ``write``. Need to open the file in
|
||||
binary mode then wrap it in a subclass that can write ``str`` and
|
||||
``unicode``.
|
||||
|
||||
Also handles not passing ``encoding`` and ``errors`` in binary mode.
|
||||
"""
|
||||
binary = "b" in mode
|
||||
|
||||
if binary:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = {"encoding": encoding, "errors": errors}
|
||||
|
||||
if not PY2 or binary:
|
||||
return io.open(file, mode, **kwargs)
|
||||
|
||||
f = io.open(file, "{}b".format(mode.replace("t", "")))
|
||||
return _make_text_stream(f, **kwargs)
|
||||
|
||||
|
||||
def open_stream(filename, mode="r", encoding=None, errors="strict", atomic=False):
|
||||
binary = "b" in mode
|
||||
|
||||
# Standard streams first. These are simple because they don't need
|
||||
# special handling for the atomic flag. It's entirely ignored.
|
||||
if filename == '-':
|
||||
if any(m in mode for m in ['w', 'a', 'x']):
|
||||
if 'b' in mode:
|
||||
if filename == "-":
|
||||
if any(m in mode for m in ["w", "a", "x"]):
|
||||
if binary:
|
||||
return get_binary_stdout(), False
|
||||
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||
if 'b' in mode:
|
||||
if binary:
|
||||
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
|
||||
return _wrap_io_open(filename, mode, encoding, errors), True
|
||||
|
||||
# Some usability stuff for atomic writes
|
||||
if 'a' in mode:
|
||||
if "a" in mode:
|
||||
raise ValueError(
|
||||
'Appending to an existing file is not supported, because that '
|
||||
'would involve an expensive `copy`-operation to a temporary '
|
||||
'file. Open the file in normal `w`-mode and copy explicitly '
|
||||
'if that\'s what you\'re after.'
|
||||
"Appending to an existing file is not supported, because that"
|
||||
" would involve an expensive `copy`-operation to a temporary"
|
||||
" file. Open the file in normal `w`-mode and copy explicitly"
|
||||
" if that's what you're after."
|
||||
)
|
||||
if 'x' in mode:
|
||||
raise ValueError('Use the `overwrite`-parameter instead.')
|
||||
if 'w' not in mode:
|
||||
raise ValueError('Atomic writes only make sense with `w`-mode.')
|
||||
if "x" in mode:
|
||||
raise ValueError("Use the `overwrite`-parameter instead.")
|
||||
if "w" not in mode:
|
||||
raise ValueError("Atomic writes only make sense with `w`-mode.")
|
||||
|
||||
# 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')
|
||||
import errno
|
||||
import random
|
||||
|
||||
if encoding is not None:
|
||||
f = io.open(fd, mode, encoding=encoding, errors=errors)
|
||||
else:
|
||||
f = os.fdopen(fd, mode)
|
||||
try:
|
||||
perm = os.stat(filename).st_mode
|
||||
except OSError:
|
||||
perm = None
|
||||
|
||||
flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
||||
|
||||
if binary:
|
||||
flags |= getattr(os, "O_BINARY", 0)
|
||||
|
||||
while True:
|
||||
tmp_filename = os.path.join(
|
||||
os.path.dirname(filename),
|
||||
".__atomic-write{:08x}".format(random.randrange(1 << 32)),
|
||||
)
|
||||
try:
|
||||
fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
|
||||
break
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST or (
|
||||
os.name == "nt"
|
||||
and e.errno == errno.EACCES
|
||||
and os.path.isdir(e.filename)
|
||||
and os.access(e.filename, os.W_OK)
|
||||
):
|
||||
continue
|
||||
raise
|
||||
|
||||
if perm is not None:
|
||||
os.chmod(tmp_filename, perm) # in case perm includes bits in umask
|
||||
|
||||
f = _wrap_io_open(fd, mode, encoding, errors)
|
||||
return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True
|
||||
|
||||
|
||||
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||
if hasattr(os, 'replace'):
|
||||
if hasattr(os, "replace"):
|
||||
_replace = os.replace
|
||||
_can_replace = True
|
||||
else:
|
||||
|
@ -526,7 +593,6 @@ else:
|
|||
|
||||
|
||||
class _AtomicFile(object):
|
||||
|
||||
def __init__(self, f, tmp_filename, real_filename):
|
||||
self._f = f
|
||||
self._tmp_filename = tmp_filename
|
||||
|
@ -568,14 +634,26 @@ get_winterm_size = None
|
|||
|
||||
|
||||
def strip_ansi(value):
|
||||
return _ansi_re.sub('', value)
|
||||
return _ansi_re.sub("", value)
|
||||
|
||||
|
||||
def _is_jupyter_kernel_output(stream):
|
||||
if WIN:
|
||||
# TODO: Couldn't test on Windows, should't try to support until
|
||||
# someone tests the details wrt colorama.
|
||||
return
|
||||
|
||||
while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
|
||||
stream = stream._stream
|
||||
|
||||
return stream.__class__.__module__.startswith("ipykernel.")
|
||||
|
||||
|
||||
def should_strip_ansi(stream=None, color=None):
|
||||
if color is None:
|
||||
if stream is None:
|
||||
stream = sys.stdin
|
||||
return not isatty(stream)
|
||||
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
|
||||
return not color
|
||||
|
||||
|
||||
|
@ -590,16 +668,18 @@ if WIN:
|
|||
|
||||
def _get_argv_encoding():
|
||||
import locale
|
||||
|
||||
return locale.getpreferredencoding()
|
||||
|
||||
if PY2:
|
||||
def raw_input(prompt=''):
|
||||
|
||||
def raw_input(prompt=""):
|
||||
sys.stderr.flush()
|
||||
if prompt:
|
||||
stdout = _default_text_stdout()
|
||||
stdout.write(prompt)
|
||||
stdin = _default_text_stdin()
|
||||
return stdin.readline().rstrip('\r\n')
|
||||
return stdin.readline().rstrip("\r\n")
|
||||
|
||||
try:
|
||||
import colorama
|
||||
|
@ -641,11 +721,15 @@ if WIN:
|
|||
|
||||
def get_winterm_size():
|
||||
win = colorama.win32.GetConsoleScreenBufferInfo(
|
||||
colorama.win32.STDOUT).srWindow
|
||||
colorama.win32.STDOUT
|
||||
).srWindow
|
||||
return win.Right - win.Left, win.Bottom - win.Top
|
||||
|
||||
|
||||
else:
|
||||
|
||||
def _get_argv_encoding():
|
||||
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
|
||||
return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding()
|
||||
|
||||
_get_windows_console_stream = lambda *x: None
|
||||
_wrap_std_stream = lambda *x: None
|
||||
|
@ -664,6 +748,7 @@ def isatty(stream):
|
|||
|
||||
def _make_cached_stream_func(src_func, wrapper_func):
|
||||
cache = WeakKeyDictionary()
|
||||
|
||||
def func():
|
||||
stream = src_func()
|
||||
try:
|
||||
|
@ -679,25 +764,23 @@ def _make_cached_stream_func(src_func, wrapper_func):
|
|||
except Exception:
|
||||
pass
|
||||
return rv
|
||||
|
||||
return func
|
||||
|
||||
|
||||
_default_text_stdin = _make_cached_stream_func(
|
||||
lambda: sys.stdin, get_text_stdin)
|
||||
_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)
|
||||
_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
|
||||
_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,
|
||||
"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,
|
||||
"stdin": get_text_stdin,
|
||||
"stdout": get_text_stdout,
|
||||
"stderr": get_text_stderr,
|
||||
}
|
|
@ -1,34 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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: © 2014 by the Pallets team.
|
||||
:license: BSD, see LICENSE.rst for more details.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import math
|
||||
import contextlib
|
||||
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
||||
open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \
|
||||
CYGWIN
|
||||
from .utils import echo
|
||||
|
||||
from ._compat import _default_text_stdout
|
||||
from ._compat import CYGWIN
|
||||
from ._compat import get_best_encoding
|
||||
from ._compat import int_types
|
||||
from ._compat import isatty
|
||||
from ._compat import open_stream
|
||||
from ._compat import range_type
|
||||
from ._compat import strip_ansi
|
||||
from ._compat import term_len
|
||||
from ._compat import WIN
|
||||
from .exceptions import ClickException
|
||||
from .utils import echo
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
BEFORE_BAR = '\r'
|
||||
AFTER_BAR = '\n'
|
||||
if os.name == "nt":
|
||||
BEFORE_BAR = "\r"
|
||||
AFTER_BAR = "\n"
|
||||
else:
|
||||
BEFORE_BAR = '\r\033[?25l'
|
||||
AFTER_BAR = '\033[?25h\n'
|
||||
BEFORE_BAR = "\r\033[?25l"
|
||||
AFTER_BAR = "\033[?25h\n"
|
||||
|
||||
|
||||
def _length_hint(obj):
|
||||
|
@ -44,19 +44,29 @@ def _length_hint(obj):
|
|||
hint = get_hint(obj)
|
||||
except TypeError:
|
||||
return None
|
||||
if hint is NotImplemented or \
|
||||
not isinstance(hint, int_types) or \
|
||||
hint < 0:
|
||||
if hint is NotImplemented or not isinstance(hint, int_types) 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, color=None, width=30):
|
||||
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,
|
||||
color=None,
|
||||
width=30,
|
||||
):
|
||||
self.fill_char = fill_char
|
||||
self.empty_char = empty_char
|
||||
self.bar_template = bar_template
|
||||
|
@ -65,7 +75,7 @@ class ProgressBar(object):
|
|||
self.show_percent = show_percent
|
||||
self.show_pos = show_pos
|
||||
self.item_show_func = item_show_func
|
||||
self.label = label or ''
|
||||
self.label = label or ""
|
||||
if file is None:
|
||||
file = _default_text_stdout()
|
||||
self.file = file
|
||||
|
@ -77,7 +87,7 @@ class ProgressBar(object):
|
|||
length = _length_hint(iterable)
|
||||
if iterable is None:
|
||||
if length is None:
|
||||
raise TypeError('iterable or length is required')
|
||||
raise TypeError("iterable or length is required")
|
||||
iterable = range_type(length)
|
||||
self.iter = iter(iterable)
|
||||
self.length = length
|
||||
|
@ -104,10 +114,21 @@ class ProgressBar(object):
|
|||
|
||||
def __iter__(self):
|
||||
if not self.entered:
|
||||
raise RuntimeError('You need to use progress bars in a with block.')
|
||||
raise RuntimeError("You need to use progress bars in a with block.")
|
||||
self.render_progress()
|
||||
return self.generator()
|
||||
|
||||
def __next__(self):
|
||||
# Iteration is defined in terms of a generator function,
|
||||
# returned by iter(self); use that to define next(). This works
|
||||
# because `self.iter` is an iterable consumed by that generator,
|
||||
# so it is re-entry safe. Calling `next(self.generator())`
|
||||
# twice works and does "what you want".
|
||||
return next(iter(self))
|
||||
|
||||
# Python 2 compat
|
||||
next = __next__
|
||||
|
||||
def is_fast(self):
|
||||
return time.time() - self.start <= self.short_limit
|
||||
|
||||
|
@ -145,20 +166,19 @@ class ProgressBar(object):
|
|||
hours = t % 24
|
||||
t //= 24
|
||||
if t > 0:
|
||||
days = t
|
||||
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
||||
return "{}d {:02}:{:02}:{:02}".format(t, hours, minutes, seconds)
|
||||
else:
|
||||
return '%02d:%02d:%02d' % (hours, minutes, seconds)
|
||||
return ''
|
||||
return "{:02}:{:02}:{:02}".format(hours, minutes, seconds)
|
||||
return ""
|
||||
|
||||
def format_pos(self):
|
||||
pos = str(self.pos)
|
||||
if self.length_known:
|
||||
pos += '/%s' % self.length
|
||||
pos += "/{}".format(self.length)
|
||||
return pos
|
||||
|
||||
def format_pct(self):
|
||||
return ('% 4d%%' % int(self.pct * 100))[1:]
|
||||
return "{: 4}%".format(int(self.pct * 100))[1:]
|
||||
|
||||
def format_bar(self):
|
||||
if self.length_known:
|
||||
|
@ -170,9 +190,13 @@ class ProgressBar(object):
|
|||
else:
|
||||
bar = list(self.empty_char * (self.width or 1))
|
||||
if self.time_per_iteration != 0:
|
||||
bar[int((math.cos(self.pos * self.time_per_iteration)
|
||||
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
||||
bar = ''.join(bar)
|
||||
bar[
|
||||
int(
|
||||
(math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
|
||||
* self.width
|
||||
)
|
||||
] = self.fill_char
|
||||
bar = "".join(bar)
|
||||
return bar
|
||||
|
||||
def format_progress_line(self):
|
||||
|
@ -193,11 +217,14 @@ class ProgressBar(object):
|
|||
if item_info is not None:
|
||||
info_bits.append(item_info)
|
||||
|
||||
return (self.bar_template % {
|
||||
'label': self.label,
|
||||
'bar': self.format_bar(),
|
||||
'info': self.info_sep.join(info_bits)
|
||||
}).rstrip()
|
||||
return (
|
||||
self.bar_template
|
||||
% {
|
||||
"label": self.label,
|
||||
"bar": self.format_bar(),
|
||||
"info": self.info_sep.join(info_bits),
|
||||
}
|
||||
).rstrip()
|
||||
|
||||
def render_progress(self):
|
||||
from .termui import get_terminal_size
|
||||
|
@ -214,7 +241,7 @@ class ProgressBar(object):
|
|||
new_width = max(0, get_terminal_size()[0] - clutter_length)
|
||||
if new_width < old_width:
|
||||
buf.append(BEFORE_BAR)
|
||||
buf.append(' ' * self.max_width)
|
||||
buf.append(" " * self.max_width)
|
||||
self.max_width = new_width
|
||||
self.width = new_width
|
||||
|
||||
|
@ -229,8 +256,8 @@ class ProgressBar(object):
|
|||
self.max_width = line_len
|
||||
|
||||
buf.append(line)
|
||||
buf.append(' ' * (clear_width - line_len))
|
||||
line = ''.join(buf)
|
||||
buf.append(" " * (clear_width - line_len))
|
||||
line = "".join(buf)
|
||||
# Render the line only if it changed.
|
||||
|
||||
if line != self._last_line and not self.is_fast():
|
||||
|
@ -270,13 +297,19 @@ class ProgressBar(object):
|
|||
self.finished = True
|
||||
|
||||
def generator(self):
|
||||
"""Return a generator which yields the items added to the bar
|
||||
during construction, and updates the progress bar *after* the
|
||||
yielded block returns.
|
||||
"""
|
||||
Returns a generator which yields the items added to the bar during
|
||||
construction, and updates the progress bar *after* the yielded block
|
||||
returns.
|
||||
"""
|
||||
# WARNING: the iterator interface for `ProgressBar` relies on
|
||||
# this and only works because this is a simple generator which
|
||||
# doesn't create or manage additional state. If this function
|
||||
# changes, the impact should be evaluated both against
|
||||
# `iter(bar)` and `next(bar)`. `next()` in particular may call
|
||||
# `self.generator()` repeatedly, and this must remain safe in
|
||||
# order for that interface to work.
|
||||
if not self.entered:
|
||||
raise RuntimeError('You need to use progress bars in a with block.')
|
||||
raise RuntimeError("You need to use progress bars in a with block.")
|
||||
|
||||
if self.is_hidden:
|
||||
for rv in self.iter:
|
||||
|
@ -295,24 +328,25 @@ def pager(generator, color=None):
|
|||
stdout = _default_text_stdout()
|
||||
if not isatty(sys.stdin) or not isatty(stdout):
|
||||
return _nullpager(stdout, generator, color)
|
||||
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
||||
pager_cmd = (os.environ.get("PAGER", None) or "").strip()
|
||||
if pager_cmd:
|
||||
if WIN:
|
||||
return _tempfilepager(generator, pager_cmd, color)
|
||||
return _pipepager(generator, pager_cmd, color)
|
||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||
if os.environ.get("TERM") in ("dumb", "emacs"):
|
||||
return _nullpager(stdout, generator, color)
|
||||
if WIN or sys.platform.startswith('os2'):
|
||||
return _tempfilepager(generator, 'more <', color)
|
||||
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
||||
return _pipepager(generator, 'less', color)
|
||||
if WIN or sys.platform.startswith("os2"):
|
||||
return _tempfilepager(generator, "more <", color)
|
||||
if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0:
|
||||
return _pipepager(generator, "less", color)
|
||||
|
||||
import tempfile
|
||||
|
||||
fd, filename = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
try:
|
||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
||||
return _pipepager(generator, 'more', color)
|
||||
if hasattr(os, "system") and os.system('more "{}"'.format(filename)) == 0:
|
||||
return _pipepager(generator, "more", color)
|
||||
return _nullpager(stdout, generator, color)
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
@ -323,28 +357,28 @@ def _pipepager(generator, cmd, color):
|
|||
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:])
|
||||
cmd_detail = cmd.rsplit("/", 1)[-1].split()
|
||||
if color is None and cmd_detail[0] == "less":
|
||||
less_flags = "{}{}".format(os.environ.get("LESS", ""), " ".join(cmd_detail[1:]))
|
||||
if not less_flags:
|
||||
env['LESS'] = '-R'
|
||||
env["LESS"] = "-R"
|
||||
color = True
|
||||
elif 'r' in less_flags or 'R' in less_flags:
|
||||
elif "r" in less_flags or "R" in less_flags:
|
||||
color = True
|
||||
|
||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||
env=env)
|
||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env)
|
||||
encoding = get_best_encoding(c.stdin)
|
||||
try:
|
||||
for text in generator:
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
|
||||
c.stdin.write(text.encode(encoding, 'replace'))
|
||||
c.stdin.write(text.encode(encoding, "replace"))
|
||||
except (IOError, KeyboardInterrupt):
|
||||
pass
|
||||
else:
|
||||
|
@ -370,16 +404,17 @@ def _pipepager(generator, cmd, color):
|
|||
def _tempfilepager(generator, cmd, color):
|
||||
"""Page through text by invoking a program on a temporary file."""
|
||||
import tempfile
|
||||
|
||||
filename = tempfile.mktemp()
|
||||
# TODO: This never terminates if the passed generator never terminates.
|
||||
text = "".join(generator)
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
encoding = get_best_encoding(sys.stdout)
|
||||
with open_stream(filename, 'wb')[0] as f:
|
||||
with open_stream(filename, "wb")[0] as f:
|
||||
f.write(text.encode(encoding))
|
||||
try:
|
||||
os.system(cmd + ' "' + filename + '"')
|
||||
os.system('{} "{}"'.format(cmd, filename))
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
@ -393,9 +428,7 @@ def _nullpager(stream, generator, color):
|
|||
|
||||
|
||||
class Editor(object):
|
||||
|
||||
def __init__(self, editor=None, env=None, require_save=True,
|
||||
extension='.txt'):
|
||||
def __init__(self, editor=None, env=None, require_save=True, extension=".txt"):
|
||||
self.editor = editor
|
||||
self.env = env
|
||||
self.require_save = require_save
|
||||
|
@ -404,19 +437,20 @@ class Editor(object):
|
|||
def get_editor(self):
|
||||
if self.editor is not None:
|
||||
return self.editor
|
||||
for key in 'VISUAL', '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 2>&1' % editor) == 0:
|
||||
return "notepad"
|
||||
for editor in "sensible-editor", "vim", "nano":
|
||||
if os.system("which {} >/dev/null 2>&1".format(editor)) == 0:
|
||||
return editor
|
||||
return 'vi'
|
||||
return "vi"
|
||||
|
||||
def edit_file(self, filename):
|
||||
import subprocess
|
||||
|
||||
editor = self.get_editor()
|
||||
if self.env:
|
||||
environ = os.environ.copy()
|
||||
|
@ -424,47 +458,47 @@ class Editor(object):
|
|||
else:
|
||||
environ = None
|
||||
try:
|
||||
c = subprocess.Popen('%s "%s"' % (editor, filename),
|
||||
env=environ, shell=True)
|
||||
c = subprocess.Popen(
|
||||
'{} "{}"'.format(editor, filename), env=environ, shell=True,
|
||||
)
|
||||
exit_code = c.wait()
|
||||
if exit_code != 0:
|
||||
raise ClickException('%s: Editing failed!' % editor)
|
||||
raise ClickException("{}: Editing failed!".format(editor))
|
||||
except OSError as e:
|
||||
raise ClickException('%s: Editing failed: %s' % (editor, e))
|
||||
raise ClickException("{}: Editing failed: {}".format(editor, e))
|
||||
|
||||
def edit(self, text):
|
||||
import tempfile
|
||||
|
||||
text = text or ''
|
||||
if text and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
text = text or ""
|
||||
if text and not text.endswith("\n"):
|
||||
text += "\n"
|
||||
|
||||
fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension)
|
||||
fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
|
||||
try:
|
||||
if WIN:
|
||||
encoding = 'utf-8-sig'
|
||||
text = text.replace('\n', '\r\n')
|
||||
encoding = "utf-8-sig"
|
||||
text = text.replace("\n", "\r\n")
|
||||
else:
|
||||
encoding = 'utf-8'
|
||||
encoding = "utf-8"
|
||||
text = text.encode(encoding)
|
||||
|
||||
f = os.fdopen(fd, 'wb')
|
||||
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:
|
||||
if self.require_save and os.path.getmtime(name) == timestamp:
|
||||
return None
|
||||
|
||||
f = open(name, 'rb')
|
||||
f = open(name, "rb")
|
||||
try:
|
||||
rv = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
return rv.decode('utf-8-sig').replace('\r\n', '\n')
|
||||
return rv.decode("utf-8-sig").replace("\r\n", "\n")
|
||||
finally:
|
||||
os.unlink(name)
|
||||
|
||||
|
@ -477,18 +511,18 @@ def open_url(url, wait=False, locate=False):
|
|||
import urllib
|
||||
except ImportError:
|
||||
import urllib
|
||||
if url.startswith('file://'):
|
||||
if url.startswith("file://"):
|
||||
url = urllib.unquote(url[7:])
|
||||
return url
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
args = ['open']
|
||||
if sys.platform == "darwin":
|
||||
args = ["open"]
|
||||
if wait:
|
||||
args.append('-W')
|
||||
args.append("-W")
|
||||
if locate:
|
||||
args.append('-R')
|
||||
args.append("-R")
|
||||
args.append(_unquote_file(url))
|
||||
null = open('/dev/null', 'w')
|
||||
null = open("/dev/null", "w")
|
||||
try:
|
||||
return subprocess.Popen(args, stderr=null).wait()
|
||||
finally:
|
||||
|
@ -496,44 +530,44 @@ def open_url(url, wait=False, locate=False):
|
|||
elif WIN:
|
||||
if locate:
|
||||
url = _unquote_file(url)
|
||||
args = 'explorer /select,"%s"' % _unquote_file(
|
||||
url.replace('"', ''))
|
||||
args = 'explorer /select,"{}"'.format(_unquote_file(url.replace('"', "")))
|
||||
else:
|
||||
args = 'start %s "" "%s"' % (
|
||||
wait and '/WAIT' or '', url.replace('"', ''))
|
||||
args = 'start {} "" "{}"'.format(
|
||||
"/WAIT" if wait else "", url.replace('"', "")
|
||||
)
|
||||
return os.system(args)
|
||||
elif CYGWIN:
|
||||
if locate:
|
||||
url = _unquote_file(url)
|
||||
args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', ''))
|
||||
args = 'cygstart "{}"'.format(os.path.dirname(url).replace('"', ""))
|
||||
else:
|
||||
args = 'cygstart %s "%s"' % (
|
||||
wait and '-w' or '', url.replace('"', ''))
|
||||
args = 'cygstart {} "{}"'.format("-w" if wait else "", url.replace('"', ""))
|
||||
return os.system(args)
|
||||
|
||||
try:
|
||||
if locate:
|
||||
url = os.path.dirname(_unquote_file(url)) or '.'
|
||||
url = os.path.dirname(_unquote_file(url)) or "."
|
||||
else:
|
||||
url = _unquote_file(url)
|
||||
c = subprocess.Popen(['xdg-open', 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:
|
||||
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 == u'\x03':
|
||||
if ch == u"\x03":
|
||||
raise KeyboardInterrupt()
|
||||
if ch == u'\x04' and not WIN: # Unix-like, Ctrl+D
|
||||
if ch == u"\x04" and not WIN: # Unix-like, Ctrl+D
|
||||
raise EOFError()
|
||||
if ch == u'\x1a' and WIN: # Windows, Ctrl+Z
|
||||
if ch == u"\x1a" and WIN: # Windows, Ctrl+Z
|
||||
raise EOFError()
|
||||
|
||||
|
||||
|
@ -580,12 +614,14 @@ if WIN:
|
|||
func = msvcrt.getwch
|
||||
|
||||
rv = func()
|
||||
if rv in (u'\x00', u'\xe0'):
|
||||
if rv in (u"\x00", u"\xe0"):
|
||||
# \x00 and \xe0 are control characters that indicate special key,
|
||||
# see above.
|
||||
rv += func()
|
||||
_translate_ch_to_exc(rv)
|
||||
return rv
|
||||
|
||||
|
||||
else:
|
||||
import tty
|
||||
import termios
|
||||
|
@ -593,7 +629,7 @@ else:
|
|||
@contextlib.contextmanager
|
||||
def raw_terminal():
|
||||
if not isatty(sys.stdin):
|
||||
f = open('/dev/tty')
|
||||
f = open("/dev/tty")
|
||||
fd = f.fileno()
|
||||
else:
|
||||
fd = sys.stdin.fileno()
|
||||
|
@ -614,7 +650,7 @@ else:
|
|||
def getchar(echo):
|
||||
with raw_terminal() as fd:
|
||||
ch = os.read(fd, 32)
|
||||
ch = ch.decode(get_best_encoding(sys.stdin), 'replace')
|
||||
ch = ch.decode(get_best_encoding(sys.stdin), "replace")
|
||||
if echo and isatty(sys.stdout):
|
||||
sys.stdout.write(ch)
|
||||
_translate_ch_to_exc(ch)
|
|
@ -3,7 +3,6 @@ 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)
|
||||
|
||||
|
@ -35,4 +34,4 @@ class TextWrapper(textwrap.TextWrapper):
|
|||
if idx > 0:
|
||||
indent = self.subsequent_indent
|
||||
rv.append(indent + line)
|
||||
return '\n'.join(rv)
|
||||
return "\n".join(rv)
|
131
src/click/_unicodefun.py
Normal file
131
src/click/_unicodefun.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
import codecs
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ._compat import PY2
|
||||
|
||||
|
||||
def _find_unicode_literals_frame():
|
||||
import __future__
|
||||
|
||||
if not hasattr(sys, "_getframe"): # not all Python implementations have it
|
||||
return 0
|
||||
frm = sys._getframe(1)
|
||||
idx = 1
|
||||
while frm is not None:
|
||||
if frm.f_globals.get("__name__", "").startswith("click."):
|
||||
frm = frm.f_back
|
||||
idx += 1
|
||||
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
||||
return idx
|
||||
else:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
def _check_for_unicode_literals():
|
||||
if not __debug__:
|
||||
return
|
||||
|
||||
from . import disable_unicode_literals_warning
|
||||
|
||||
if not PY2 or disable_unicode_literals_warning:
|
||||
return
|
||||
bad_frame = _find_unicode_literals_frame()
|
||||
if bad_frame <= 0:
|
||||
return
|
||||
from warnings import warn
|
||||
|
||||
warn(
|
||||
Warning(
|
||||
"Click detected the use of the unicode_literals __future__"
|
||||
" import. This is heavily discouraged because it can"
|
||||
" introduce subtle bugs in your code. You should instead"
|
||||
' use explicit u"" literals for your unicode strings. For'
|
||||
" more information see"
|
||||
" https://click.palletsprojects.com/python3/"
|
||||
),
|
||||
stacklevel=bad_frame,
|
||||
)
|
||||
|
||||
|
||||
def _verify_python3_env():
|
||||
"""Ensures that the environment is good for unicode on Python 3."""
|
||||
if PY2:
|
||||
return
|
||||
try:
|
||||
import locale
|
||||
|
||||
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
||||
except Exception:
|
||||
fs_enc = "ascii"
|
||||
if fs_enc != "ascii":
|
||||
return
|
||||
|
||||
extra = ""
|
||||
if os.name == "posix":
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
rv = subprocess.Popen(
|
||||
["locale", "-a"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
).communicate()[0]
|
||||
except OSError:
|
||||
rv = b""
|
||||
good_locales = set()
|
||||
has_c_utf8 = False
|
||||
|
||||
# Make sure we're operating on text here.
|
||||
if isinstance(rv, bytes):
|
||||
rv = rv.decode("ascii", "replace")
|
||||
|
||||
for line in rv.splitlines():
|
||||
locale = line.strip()
|
||||
if locale.lower().endswith((".utf-8", ".utf8")):
|
||||
good_locales.add(locale)
|
||||
if locale.lower() in ("c.utf8", "c.utf-8"):
|
||||
has_c_utf8 = True
|
||||
|
||||
extra += "\n\n"
|
||||
if not good_locales:
|
||||
extra += (
|
||||
"Additional information: on this system no suitable"
|
||||
" UTF-8 locales were discovered. This most likely"
|
||||
" requires resolving by reconfiguring the locale"
|
||||
" system."
|
||||
)
|
||||
elif has_c_utf8:
|
||||
extra += (
|
||||
"This system supports the C.UTF-8 locale which is"
|
||||
" recommended. You might be able to resolve your issue"
|
||||
" by exporting the following environment variables:\n\n"
|
||||
" export LC_ALL=C.UTF-8\n"
|
||||
" export LANG=C.UTF-8"
|
||||
)
|
||||
else:
|
||||
extra += (
|
||||
"This system lists a couple of UTF-8 supporting locales"
|
||||
" that you can pick from. The following suitable"
|
||||
" locales were discovered: {}".format(", ".join(sorted(good_locales)))
|
||||
)
|
||||
|
||||
bad_locale = None
|
||||
for locale in os.environ.get("LC_ALL"), os.environ.get("LANG"):
|
||||
if locale and locale.lower().endswith((".utf-8", ".utf8")):
|
||||
bad_locale = locale
|
||||
if locale is not None:
|
||||
break
|
||||
if bad_locale is not None:
|
||||
extra += (
|
||||
"\n\nClick discovered that you exported a UTF-8 locale"
|
||||
" but the locale system could not pick up from it"
|
||||
" because it does not exist. The exported locale is"
|
||||
" '{}' but it is not supported".format(bad_locale)
|
||||
)
|
||||
|
||||
raise RuntimeError(
|
||||
"Click will abort further execution because Python 3 was"
|
||||
" configured to use ASCII as encoding for the environment."
|
||||
" Consult https://click.palletsprojects.com/python3/ for"
|
||||
" mitigation steps.{}".format(extra)
|
||||
)
|
|
@ -7,24 +7,42 @@
|
|||
# compared to the original patches as we do not need to patch
|
||||
# the entire interpreter but just work in our little world of
|
||||
# echo and prmopt.
|
||||
|
||||
import ctypes
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import time
|
||||
import ctypes
|
||||
import zlib
|
||||
from ctypes import byref
|
||||
from ctypes import c_char
|
||||
from ctypes import c_char_p
|
||||
from ctypes import c_int
|
||||
from ctypes import c_ssize_t
|
||||
from ctypes import c_ulong
|
||||
from ctypes import c_void_p
|
||||
from ctypes import POINTER
|
||||
from ctypes import py_object
|
||||
from ctypes import windll
|
||||
from ctypes import WinError
|
||||
from ctypes import WINFUNCTYPE
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import HANDLE
|
||||
from ctypes.wintypes import LPCWSTR
|
||||
from ctypes.wintypes import LPWSTR
|
||||
|
||||
import msvcrt
|
||||
from ._compat import _NonClosingTextIOWrapper, text_type, PY2
|
||||
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
|
||||
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
||||
|
||||
from ._compat import _NonClosingTextIOWrapper
|
||||
from ._compat import PY2
|
||||
from ._compat import text_type
|
||||
|
||||
try:
|
||||
from ctypes import pythonapi
|
||||
|
||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||
except ImportError:
|
||||
pythonapi = None
|
||||
from ctypes.wintypes import LPWSTR, LPCWSTR
|
||||
|
||||
|
||||
c_ssize_p = POINTER(c_ssize_t)
|
||||
|
@ -33,12 +51,15 @@ kernel32 = windll.kernel32
|
|||
GetStdHandle = kernel32.GetStdHandle
|
||||
ReadConsoleW = kernel32.ReadConsoleW
|
||||
WriteConsoleW = kernel32.WriteConsoleW
|
||||
GetConsoleMode = kernel32.GetConsoleMode
|
||||
GetLastError = kernel32.GetLastError
|
||||
GetCommandLineW = WINFUNCTYPE(LPWSTR)(
|
||||
('GetCommandLineW', windll.kernel32))
|
||||
CommandLineToArgvW = WINFUNCTYPE(
|
||||
POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
||||
('CommandLineToArgvW', windll.shell32))
|
||||
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
|
||||
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
||||
("CommandLineToArgvW", windll.shell32)
|
||||
)
|
||||
LocalFree = WINFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)(
|
||||
("LocalFree", windll.kernel32)
|
||||
)
|
||||
|
||||
|
||||
STDIN_HANDLE = GetStdHandle(-10)
|
||||
|
@ -57,27 +78,27 @@ STDIN_FILENO = 0
|
|||
STDOUT_FILENO = 1
|
||||
STDERR_FILENO = 2
|
||||
|
||||
EOF = b'\x1a'
|
||||
EOF = b"\x1a"
|
||||
MAX_BYTES_WRITTEN = 32767
|
||||
|
||||
|
||||
class Py_buffer(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('buf', c_void_p),
|
||||
('obj', py_object),
|
||||
('len', c_ssize_t),
|
||||
('itemsize', c_ssize_t),
|
||||
('readonly', c_int),
|
||||
('ndim', c_int),
|
||||
('format', c_char_p),
|
||||
('shape', c_ssize_p),
|
||||
('strides', c_ssize_p),
|
||||
('suboffsets', c_ssize_p),
|
||||
('internal', c_void_p)
|
||||
("buf", c_void_p),
|
||||
("obj", py_object),
|
||||
("len", c_ssize_t),
|
||||
("itemsize", c_ssize_t),
|
||||
("readonly", c_int),
|
||||
("ndim", c_int),
|
||||
("format", c_char_p),
|
||||
("shape", c_ssize_p),
|
||||
("strides", c_ssize_p),
|
||||
("suboffsets", c_ssize_p),
|
||||
("internal", c_void_p),
|
||||
]
|
||||
|
||||
if PY2:
|
||||
_fields_.insert(-1, ('smalltable', c_ssize_t * 2))
|
||||
_fields_.insert(-1, ("smalltable", c_ssize_t * 2))
|
||||
|
||||
|
||||
# On PyPy we cannot get buffers so our ability to operate here is
|
||||
|
@ -85,6 +106,7 @@ class Py_buffer(ctypes.Structure):
|
|||
if pythonapi is None:
|
||||
get_buffer = None
|
||||
else:
|
||||
|
||||
def get_buffer(obj, writable=False):
|
||||
buf = Py_buffer()
|
||||
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
|
||||
|
@ -97,7 +119,6 @@ else:
|
|||
|
||||
|
||||
class _WindowsConsoleRawIOBase(io.RawIOBase):
|
||||
|
||||
def __init__(self, handle):
|
||||
self.handle = handle
|
||||
|
||||
|
@ -107,7 +128,6 @@ class _WindowsConsoleRawIOBase(io.RawIOBase):
|
|||
|
||||
|
||||
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
|
@ -116,20 +136,26 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
|||
if not bytes_to_be_read:
|
||||
return 0
|
||||
elif bytes_to_be_read % 2:
|
||||
raise ValueError('cannot read odd number of bytes from '
|
||||
'UTF-16-LE encoded console')
|
||||
raise ValueError(
|
||||
"cannot read odd number of bytes from UTF-16-LE encoded console"
|
||||
)
|
||||
|
||||
buffer = get_buffer(b, writable=True)
|
||||
code_units_to_be_read = bytes_to_be_read // 2
|
||||
code_units_read = c_ulong()
|
||||
|
||||
rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read,
|
||||
byref(code_units_read), None)
|
||||
rv = ReadConsoleW(
|
||||
HANDLE(self.handle),
|
||||
buffer,
|
||||
code_units_to_be_read,
|
||||
byref(code_units_read),
|
||||
None,
|
||||
)
|
||||
if GetLastError() == ERROR_OPERATION_ABORTED:
|
||||
# wait for KeyboardInterrupt
|
||||
time.sleep(0.1)
|
||||
if not rv:
|
||||
raise OSError('Windows error: %s' % GetLastError())
|
||||
raise OSError("Windows error: {}".format(GetLastError()))
|
||||
|
||||
if buffer[0] == EOF:
|
||||
return 0
|
||||
|
@ -137,27 +163,30 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
|||
|
||||
|
||||
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_error_message(errno):
|
||||
if errno == ERROR_SUCCESS:
|
||||
return 'ERROR_SUCCESS'
|
||||
return "ERROR_SUCCESS"
|
||||
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
||||
return 'ERROR_NOT_ENOUGH_MEMORY'
|
||||
return 'Windows error %s' % errno
|
||||
return "ERROR_NOT_ENOUGH_MEMORY"
|
||||
return "Windows error {}".format(errno)
|
||||
|
||||
def write(self, b):
|
||||
bytes_to_be_written = len(b)
|
||||
buf = get_buffer(b)
|
||||
code_units_to_be_written = min(bytes_to_be_written,
|
||||
MAX_BYTES_WRITTEN) // 2
|
||||
code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
|
||||
code_units_written = c_ulong()
|
||||
|
||||
WriteConsoleW(self.handle, buf, code_units_to_be_written,
|
||||
byref(code_units_written), None)
|
||||
WriteConsoleW(
|
||||
HANDLE(self.handle),
|
||||
buf,
|
||||
code_units_to_be_written,
|
||||
byref(code_units_written),
|
||||
None,
|
||||
)
|
||||
bytes_written = 2 * code_units_written.value
|
||||
|
||||
if bytes_written == 0 and bytes_to_be_written > 0:
|
||||
|
@ -166,7 +195,6 @@ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
|||
|
||||
|
||||
class ConsoleStream(object):
|
||||
|
||||
def __init__(self, text_stream, byte_stream):
|
||||
self._text_stream = text_stream
|
||||
self.buffer = byte_stream
|
||||
|
@ -195,9 +223,8 @@ class ConsoleStream(object):
|
|||
return self.buffer.isatty()
|
||||
|
||||
def __repr__(self):
|
||||
return '<ConsoleStream name=%r encoding=%r>' % (
|
||||
self.name,
|
||||
self.encoding,
|
||||
return "<ConsoleStream name={!r} encoding={!r}>".format(
|
||||
self.name, self.encoding
|
||||
)
|
||||
|
||||
|
||||
|
@ -207,6 +234,7 @@ class WindowsChunkedWriter(object):
|
|||
attribute access apart from method 'write()' which we wrap to write in
|
||||
limited chunks due to a Windows limitation on binary console streams.
|
||||
"""
|
||||
|
||||
def __init__(self, wrapped):
|
||||
# double-underscore everything to prevent clashes with names of
|
||||
# attributes on the wrapped stream object.
|
||||
|
@ -221,7 +249,7 @@ class WindowsChunkedWriter(object):
|
|||
|
||||
while written < total_to_write:
|
||||
to_write = min(total_to_write - written, MAX_BYTES_WRITTEN)
|
||||
self.__wrapped.write(text[written:written+to_write])
|
||||
self.__wrapped.write(text[written : written + to_write])
|
||||
written += to_write
|
||||
|
||||
|
||||
|
@ -230,7 +258,11 @@ _wrapped_std_streams = set()
|
|||
|
||||
def _wrap_std_stream(name):
|
||||
# Python 2 & Windows 7 and below
|
||||
if PY2 and sys.getwindowsversion()[:2] <= (6, 1) and name not in _wrapped_std_streams:
|
||||
if (
|
||||
PY2
|
||||
and sys.getwindowsversion()[:2] <= (6, 1)
|
||||
and name not in _wrapped_std_streams
|
||||
):
|
||||
setattr(sys, name, WindowsChunkedWriter(getattr(sys, name)))
|
||||
_wrapped_std_streams.add(name)
|
||||
|
||||
|
@ -238,43 +270,59 @@ def _wrap_std_stream(name):
|
|||
def _get_text_stdin(buffer_stream):
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
||||
'utf-16-le', 'strict', line_buffering=True)
|
||||
"utf-16-le",
|
||||
"strict",
|
||||
line_buffering=True,
|
||||
)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
|
||||
|
||||
def _get_text_stdout(buffer_stream):
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
||||
'utf-16-le', 'strict', line_buffering=True)
|
||||
"utf-16-le",
|
||||
"strict",
|
||||
line_buffering=True,
|
||||
)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
|
||||
|
||||
def _get_text_stderr(buffer_stream):
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
||||
'utf-16-le', 'strict', line_buffering=True)
|
||||
"utf-16-le",
|
||||
"strict",
|
||||
line_buffering=True,
|
||||
)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
|
||||
|
||||
if PY2:
|
||||
|
||||
def _hash_py_argv():
|
||||
return zlib.crc32('\x00'.join(sys.argv[1:]))
|
||||
return zlib.crc32("\x00".join(sys.argv[1:]))
|
||||
|
||||
_initial_argv_hash = _hash_py_argv()
|
||||
|
||||
def _get_windows_argv():
|
||||
argc = c_int(0)
|
||||
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
|
||||
argv = [argv_unicode[i] for i in range(0, argc.value)]
|
||||
if not argv_unicode:
|
||||
raise WinError()
|
||||
try:
|
||||
argv = [argv_unicode[i] for i in range(0, argc.value)]
|
||||
finally:
|
||||
LocalFree(argv_unicode)
|
||||
del argv_unicode
|
||||
|
||||
if not hasattr(sys, 'frozen'):
|
||||
if not hasattr(sys, "frozen"):
|
||||
argv = argv[1:]
|
||||
while len(argv) > 0:
|
||||
arg = argv[0]
|
||||
if not arg.startswith('-') or arg == '-':
|
||||
if not arg.startswith("-") or arg == "-":
|
||||
break
|
||||
argv = argv[1:]
|
||||
if arg.startswith(('-c', '-m')):
|
||||
if arg.startswith(("-c", "-m")):
|
||||
break
|
||||
|
||||
return argv[1:]
|
||||
|
@ -287,15 +335,30 @@ _stream_factories = {
|
|||
}
|
||||
|
||||
|
||||
def _is_console(f):
|
||||
if not hasattr(f, "fileno"):
|
||||
return False
|
||||
|
||||
try:
|
||||
fileno = f.fileno()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
handle = msvcrt.get_osfhandle(fileno)
|
||||
return bool(GetConsoleMode(handle, byref(DWORD())))
|
||||
|
||||
|
||||
def _get_windows_console_stream(f, encoding, errors):
|
||||
if get_buffer is not None and \
|
||||
encoding in ('utf-16-le', None) \
|
||||
and errors in ('strict', None) and \
|
||||
hasattr(f, 'isatty') and f.isatty():
|
||||
if (
|
||||
get_buffer is not None
|
||||
and encoding in ("utf-16-le", None)
|
||||
and errors in ("strict", None)
|
||||
and _is_console(f)
|
||||
):
|
||||
func = _stream_factories.get(f.fileno())
|
||||
if func is not None:
|
||||
if not PY2:
|
||||
f = getattr(f, 'buffer', None)
|
||||
f = getattr(f, "buffer", None)
|
||||
if f is None:
|
||||
return None
|
||||
else:
|
|
@ -3,37 +3,51 @@ import inspect
|
|||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from itertools import repeat
|
||||
from functools import update_wrapper
|
||||
from itertools import repeat
|
||||
|
||||
from .types import convert_type, IntRange, BOOL
|
||||
from .utils import PacifyFlushWrapper, make_str, make_default_short_help, \
|
||||
echo, get_os_args
|
||||
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
||||
MissingParameter, Exit
|
||||
from .termui import prompt, confirm, style
|
||||
from .formatting import HelpFormatter, join_options
|
||||
from .parser import OptionParser, split_opt
|
||||
from .globals import push_context, pop_context
|
||||
|
||||
from ._compat import PY2, isidentifier, iteritems, string_types
|
||||
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
|
||||
|
||||
from ._compat import isidentifier
|
||||
from ._compat import iteritems
|
||||
from ._compat import PY2
|
||||
from ._compat import string_types
|
||||
from ._unicodefun import _check_for_unicode_literals
|
||||
from ._unicodefun import _verify_python3_env
|
||||
from .exceptions import Abort
|
||||
from .exceptions import BadParameter
|
||||
from .exceptions import ClickException
|
||||
from .exceptions import Exit
|
||||
from .exceptions import MissingParameter
|
||||
from .exceptions import UsageError
|
||||
from .formatting import HelpFormatter
|
||||
from .formatting import join_options
|
||||
from .globals import pop_context
|
||||
from .globals import push_context
|
||||
from .parser import OptionParser
|
||||
from .parser import split_opt
|
||||
from .termui import confirm
|
||||
from .termui import prompt
|
||||
from .termui import style
|
||||
from .types import BOOL
|
||||
from .types import convert_type
|
||||
from .types import IntRange
|
||||
from .utils import echo
|
||||
from .utils import get_os_args
|
||||
from .utils import make_default_short_help
|
||||
from .utils import make_str
|
||||
from .utils import PacifyFlushWrapper
|
||||
|
||||
_missing = object()
|
||||
|
||||
SUBCOMMAND_METAVAR = "COMMAND [ARGS]..."
|
||||
SUBCOMMANDS_METAVAR = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..."
|
||||
|
||||
SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
|
||||
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
|
||||
|
||||
DEPRECATED_HELP_NOTICE = ' (DEPRECATED)'
|
||||
DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \
|
||||
'The command %(name)s is deprecated.'
|
||||
DEPRECATED_HELP_NOTICE = " (DEPRECATED)"
|
||||
DEPRECATED_INVOKE_NOTICE = "DeprecationWarning: The command %(name)s is deprecated."
|
||||
|
||||
|
||||
def _maybe_show_deprecated_notice(cmd):
|
||||
if cmd.deprecated:
|
||||
echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True)
|
||||
echo(style(DEPRECATED_INVOKE_NOTICE % {"name": cmd.name}, fg="red"), err=True)
|
||||
|
||||
|
||||
def fast_exit(code):
|
||||
|
@ -48,12 +62,13 @@ def fast_exit(code):
|
|||
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_var = "_{}_COMPLETE".format(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):
|
||||
fast_exit(1)
|
||||
|
||||
|
@ -62,19 +77,28 @@ def _check_multicommand(base_command, cmd_name, cmd, register=False):
|
|||
if not base_command.chain or not isinstance(cmd, MultiCommand):
|
||||
return
|
||||
if register:
|
||||
hint = 'It is not possible to add multi commands as children to ' \
|
||||
'another multi command that is in chain mode'
|
||||
hint = (
|
||||
"It is not possible to add multi commands as children to"
|
||||
" another multi command that is in chain mode."
|
||||
)
|
||||
else:
|
||||
hint = 'Found a multi command as subcommand to a multi command ' \
|
||||
'that is in chain mode. This is not supported'
|
||||
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
|
||||
'added as subcommand but it in itself is a '
|
||||
'multi command. ("%s" is a %s within a chained '
|
||||
'%s named "%s").' % (
|
||||
hint, base_command.name, cmd_name,
|
||||
cmd_name, cmd.__class__.__name__,
|
||||
base_command.__class__.__name__,
|
||||
base_command.name))
|
||||
hint = (
|
||||
"Found a multi command as subcommand to a multi command"
|
||||
" that is in chain mode. This is not supported."
|
||||
)
|
||||
raise RuntimeError(
|
||||
"{}. Command '{}' is set to chain and '{}' was added as"
|
||||
" subcommand but it in itself is a multi command. ('{}' is a {}"
|
||||
" within a chained {} named '{}').".format(
|
||||
hint,
|
||||
base_command.name,
|
||||
cmd_name,
|
||||
cmd_name,
|
||||
cmd.__class__.__name__,
|
||||
base_command.__class__.__name__,
|
||||
base_command.name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def batch(iterable, batch_size):
|
||||
|
@ -82,25 +106,26 @@ def batch(iterable, batch_size):
|
|||
|
||||
|
||||
def invoke_param_callback(callback, ctx, param, value):
|
||||
code = getattr(callback, '__code__', None)
|
||||
args = getattr(code, 'co_argcount', 3)
|
||||
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)
|
||||
|
||||
warn(
|
||||
"Parameter callbacks take 3 args, (ctx, param, value). The"
|
||||
" 2-arg style is deprecated and will be removed in 8.0.".format(callback),
|
||||
DeprecationWarning,
|
||||
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.
|
||||
"""
|
||||
"""Context manager that attaches extra information to exceptions."""
|
||||
try:
|
||||
yield
|
||||
except BadParameter as e:
|
||||
|
@ -120,11 +145,12 @@ def iter_params_for_processing(invocation_order, declaration_order):
|
|||
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')
|
||||
idx = float("inf")
|
||||
return (not item.is_eager, idx)
|
||||
|
||||
return sorted(declaration_order, key=sort_key)
|
||||
|
@ -154,6 +180,9 @@ class Context(object):
|
|||
Added the `color`, `ignore_unknown_options`, and
|
||||
`max_content_width` parameters.
|
||||
|
||||
.. versionadded:: 7.1
|
||||
Added the `show_default` parameter.
|
||||
|
||||
:param command: the command class for this context.
|
||||
:param parent: the parent context.
|
||||
:param info_name: the info name for this invocation. Generally this
|
||||
|
@ -208,15 +237,30 @@ class Context(object):
|
|||
codes are used in texts that Click prints which is by
|
||||
default not the case. This for instance would affect
|
||||
help output.
|
||||
:param show_default: if True, shows defaults for all options.
|
||||
Even if an option is later created with show_default=False,
|
||||
this command-level setting overrides it.
|
||||
"""
|
||||
|
||||
def __init__(self, command, parent=None, info_name=None, obj=None,
|
||||
auto_envvar_prefix=None, default_map=None,
|
||||
terminal_width=None, max_content_width=None,
|
||||
resilient_parsing=False, allow_extra_args=None,
|
||||
allow_interspersed_args=None,
|
||||
ignore_unknown_options=None, help_option_names=None,
|
||||
token_normalize_func=None, color=None):
|
||||
def __init__(
|
||||
self,
|
||||
command,
|
||||
parent=None,
|
||||
info_name=None,
|
||||
obj=None,
|
||||
auto_envvar_prefix=None,
|
||||
default_map=None,
|
||||
terminal_width=None,
|
||||
max_content_width=None,
|
||||
resilient_parsing=False,
|
||||
allow_extra_args=None,
|
||||
allow_interspersed_args=None,
|
||||
ignore_unknown_options=None,
|
||||
help_option_names=None,
|
||||
token_normalize_func=None,
|
||||
color=None,
|
||||
show_default=None,
|
||||
):
|
||||
#: the parent context or `None` if none exists.
|
||||
self.parent = parent
|
||||
#: the :class:`Command` for this context.
|
||||
|
@ -237,12 +281,14 @@ class Context(object):
|
|||
obj = parent.obj
|
||||
#: the user object stored.
|
||||
self.obj = obj
|
||||
self._meta = getattr(parent, 'meta', {})
|
||||
self._meta = getattr(parent, "meta", {})
|
||||
|
||||
#: 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:
|
||||
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
|
||||
|
||||
|
@ -301,7 +347,7 @@ class Context(object):
|
|||
if parent is not None:
|
||||
help_option_names = parent.help_option_names
|
||||
else:
|
||||
help_option_names = ['--help']
|
||||
help_option_names = ["--help"]
|
||||
|
||||
#: The names for the help options.
|
||||
self.help_option_names = help_option_names
|
||||
|
@ -322,13 +368,18 @@ class Context(object):
|
|||
# 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())
|
||||
if (
|
||||
parent is not None
|
||||
and parent.auto_envvar_prefix is not None
|
||||
and self.info_name is not None
|
||||
):
|
||||
auto_envvar_prefix = "{}_{}".format(
|
||||
parent.auto_envvar_prefix, self.info_name.upper()
|
||||
)
|
||||
else:
|
||||
auto_envvar_prefix = auto_envvar_prefix.upper()
|
||||
if auto_envvar_prefix is not None:
|
||||
auto_envvar_prefix = auto_envvar_prefix.replace("-", "_")
|
||||
self.auto_envvar_prefix = auto_envvar_prefix
|
||||
|
||||
if color is None and parent is not None:
|
||||
|
@ -337,6 +388,8 @@ class Context(object):
|
|||
#: Controls if styling output is wanted or not.
|
||||
self.color = color
|
||||
|
||||
self.show_default = show_default
|
||||
|
||||
self._close_callbacks = []
|
||||
self._depth = 0
|
||||
|
||||
|
@ -404,7 +457,7 @@ class Context(object):
|
|||
|
||||
Example usage::
|
||||
|
||||
LANG_KEY = __name__ + '.lang'
|
||||
LANG_KEY = f'{__name__}.lang'
|
||||
|
||||
def set_language(value):
|
||||
ctx = get_current_context()
|
||||
|
@ -419,8 +472,9 @@ class Context(object):
|
|||
|
||||
def make_formatter(self):
|
||||
"""Creates the formatter for the help and usage output."""
|
||||
return HelpFormatter(width=self.terminal_width,
|
||||
max_width=self.max_content_width)
|
||||
return HelpFormatter(
|
||||
width=self.terminal_width, max_width=self.max_content_width
|
||||
)
|
||||
|
||||
def call_on_close(self, f):
|
||||
"""This decorator remembers a function as callback that should be
|
||||
|
@ -446,11 +500,11 @@ class Context(object):
|
|||
information on the help page. It's automatically created by
|
||||
combining the info names of the chain of contexts to the root.
|
||||
"""
|
||||
rv = ''
|
||||
rv = ""
|
||||
if self.info_name is not None:
|
||||
rv = self.info_name
|
||||
if self.parent is not None:
|
||||
rv = self.parent.command_path + ' ' + rv
|
||||
rv = "{} {}".format(self.parent.command_path, rv)
|
||||
return rv.lstrip()
|
||||
|
||||
def find_root(self):
|
||||
|
@ -515,7 +569,7 @@ class Context(object):
|
|||
"""
|
||||
return self.command.get_help(self)
|
||||
|
||||
def invoke(*args, **kwargs):
|
||||
def invoke(*args, **kwargs): # noqa: B902
|
||||
"""Invokes a command callback in exactly the way it expects. There
|
||||
are two ways to invoke this method:
|
||||
|
||||
|
@ -542,8 +596,9 @@ class Context(object):
|
|||
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.')
|
||||
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:
|
||||
|
@ -554,7 +609,7 @@ class Context(object):
|
|||
with ctx:
|
||||
return callback(*args, **kwargs)
|
||||
|
||||
def forward(*args, **kwargs):
|
||||
def forward(*args, **kwargs): # noqa: B902
|
||||
"""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.
|
||||
|
@ -564,7 +619,7 @@ class Context(object):
|
|||
# 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.')
|
||||
raise TypeError("Callback is not a command.")
|
||||
|
||||
for param in self.params:
|
||||
if param not in kwargs:
|
||||
|
@ -594,6 +649,7 @@ class BaseCommand(object):
|
|||
:param context_settings: an optional dictionary with defaults that are
|
||||
passed to the context object.
|
||||
"""
|
||||
|
||||
#: the default for the :attr:`Context.allow_extra_args` flag.
|
||||
allow_extra_args = False
|
||||
#: the default for the :attr:`Context.allow_interspersed_args` flag.
|
||||
|
@ -612,11 +668,14 @@ class BaseCommand(object):
|
|||
#: an optional dictionary with defaults passed to the context.
|
||||
self.context_settings = context_settings
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {}>".format(self.__class__.__name__, self.name)
|
||||
|
||||
def get_usage(self, ctx):
|
||||
raise NotImplementedError('Base commands cannot get usage')
|
||||
raise NotImplementedError("Base commands cannot get usage")
|
||||
|
||||
def get_help(self, ctx):
|
||||
raise NotImplementedError('Base commands cannot get help')
|
||||
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
|
||||
|
@ -646,17 +705,22 @@ class BaseCommand(object):
|
|||
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.')
|
||||
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')
|
||||
raise NotImplementedError("Base commands are not invokable by default")
|
||||
|
||||
def main(self, args=None, prog_name=None, complete_var=None,
|
||||
standalone_mode=True, **extra):
|
||||
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``
|
||||
|
@ -703,8 +767,9 @@ class BaseCommand(object):
|
|||
args = list(args)
|
||||
|
||||
if prog_name is None:
|
||||
prog_name = make_str(os.path.basename(
|
||||
sys.argv and sys.argv[0] or __file__))
|
||||
prog_name = make_str(
|
||||
os.path.basename(sys.argv[0] if sys.argv else __file__)
|
||||
)
|
||||
|
||||
# Hook for the Bash completion. This only activates if the Bash
|
||||
# completion is actually enabled, otherwise this is quite a fast
|
||||
|
@ -756,7 +821,7 @@ class BaseCommand(object):
|
|||
except Abort:
|
||||
if not standalone_mode:
|
||||
raise
|
||||
echo('Aborted!', file=sys.stderr)
|
||||
echo("Aborted!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
|
@ -771,6 +836,8 @@ class Command(BaseCommand):
|
|||
|
||||
.. versionchanged:: 2.0
|
||||
Added the `context_settings` parameter.
|
||||
.. versionchanged:: 7.1
|
||||
Added the `no_args_is_help` parameter.
|
||||
|
||||
:param name: the name of the command to use unless a group overrides it.
|
||||
:param context_settings: an optional dictionary with defaults that are
|
||||
|
@ -785,16 +852,31 @@ class Command(BaseCommand):
|
|||
shown on the command listing of the parent command.
|
||||
:param add_help_option: by default each command registers a ``--help``
|
||||
option. This can be disabled by this parameter.
|
||||
:param no_args_is_help: this controls what happens if no arguments are
|
||||
provided. This option is disabled by default.
|
||||
If enabled this will add ``--help`` as argument
|
||||
if no arguments are passed
|
||||
:param hidden: hide this command from help outputs.
|
||||
|
||||
:param deprecated: issues a message indicating that
|
||||
the command is deprecated.
|
||||
"""
|
||||
|
||||
def __init__(self, name, context_settings=None, callback=None,
|
||||
params=None, help=None, epilog=None, short_help=None,
|
||||
options_metavar='[OPTIONS]', add_help_option=True,
|
||||
hidden=False, deprecated=False):
|
||||
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,
|
||||
no_args_is_help=False,
|
||||
hidden=False,
|
||||
deprecated=False,
|
||||
):
|
||||
BaseCommand.__init__(self, name, context_settings)
|
||||
#: the callback to execute when the command fires. This might be
|
||||
#: `None` in which case nothing happens.
|
||||
|
@ -805,20 +887,25 @@ class Command(BaseCommand):
|
|||
self.params = params or []
|
||||
# if a form feed (page break) is found in the help text, truncate help
|
||||
# text to the content preceding the first form feed
|
||||
if help and '\f' in help:
|
||||
help = help.split('\f', 1)[0]
|
||||
if help and "\f" in help:
|
||||
help = help.split("\f", 1)[0]
|
||||
self.help = help
|
||||
self.epilog = epilog
|
||||
self.options_metavar = options_metavar
|
||||
self.short_help = short_help
|
||||
self.add_help_option = add_help_option
|
||||
self.no_args_is_help = no_args_is_help
|
||||
self.hidden = hidden
|
||||
self.deprecated = deprecated
|
||||
|
||||
def get_usage(self, ctx):
|
||||
"""Formats the usage line into a string and returns it.
|
||||
|
||||
Calls :meth:`format_usage` internally.
|
||||
"""
|
||||
formatter = ctx.make_formatter()
|
||||
self.format_usage(ctx, formatter)
|
||||
return formatter.getvalue().rstrip('\n')
|
||||
return formatter.getvalue().rstrip("\n")
|
||||
|
||||
def get_params(self, ctx):
|
||||
rv = self.params
|
||||
|
@ -828,9 +915,12 @@ class Command(BaseCommand):
|
|||
return rv
|
||||
|
||||
def format_usage(self, ctx, formatter):
|
||||
"""Writes the usage line into the formatter."""
|
||||
"""Writes the usage line into the formatter.
|
||||
|
||||
This is a low-level method called by :meth:`get_usage`.
|
||||
"""
|
||||
pieces = self.collect_usage_pieces(ctx)
|
||||
formatter.write_usage(ctx.command_path, ' '.join(pieces))
|
||||
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
|
||||
|
@ -859,10 +949,15 @@ class Command(BaseCommand):
|
|||
if value and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help(), color=ctx.color)
|
||||
ctx.exit()
|
||||
return Option(help_options, is_flag=True,
|
||||
is_eager=True, expose_value=False,
|
||||
callback=show_help,
|
||||
help='Show this message and 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."""
|
||||
|
@ -872,21 +967,31 @@ class Command(BaseCommand):
|
|||
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:
|
||||
"""Formats the help into a string and returns it.
|
||||
|
||||
Calls :meth:`format_help` internally.
|
||||
"""
|
||||
formatter = ctx.make_formatter()
|
||||
self.format_help(ctx, formatter)
|
||||
return formatter.getvalue().rstrip('\n')
|
||||
return formatter.getvalue().rstrip("\n")
|
||||
|
||||
def get_short_help_str(self, limit=45):
|
||||
"""Gets short help for the command or makes it by shortening the long help string."""
|
||||
return self.short_help or self.help and make_default_short_help(self.help, limit) or ''
|
||||
"""Gets short help for the command or makes it by shortening the
|
||||
long help string.
|
||||
"""
|
||||
return (
|
||||
self.short_help
|
||||
or self.help
|
||||
and make_default_short_help(self.help, limit)
|
||||
or ""
|
||||
)
|
||||
|
||||
def format_help(self, ctx, formatter):
|
||||
"""Writes the help into the formatter if it exists.
|
||||
|
||||
This calls into the following methods:
|
||||
This is a low-level method called by :meth:`get_help`.
|
||||
|
||||
This calls the following methods:
|
||||
|
||||
- :meth:`format_usage`
|
||||
- :meth:`format_help_text`
|
||||
|
@ -921,7 +1026,7 @@ class Command(BaseCommand):
|
|||
opts.append(rv)
|
||||
|
||||
if opts:
|
||||
with formatter.section('Options'):
|
||||
with formatter.section("Options"):
|
||||
formatter.write_dl(opts)
|
||||
|
||||
def format_epilog(self, ctx, formatter):
|
||||
|
@ -932,17 +1037,22 @@ class Command(BaseCommand):
|
|||
formatter.write_text(self.epilog)
|
||||
|
||||
def parse_args(self, ctx, args):
|
||||
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help(), color=ctx.color)
|
||||
ctx.exit()
|
||||
|
||||
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)):
|
||||
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.fail(
|
||||
"Got unexpected extra argument{} ({})".format(
|
||||
"s" if len(args) != 1 else "", " ".join(map(make_str, args))
|
||||
)
|
||||
)
|
||||
|
||||
ctx.args = args
|
||||
return args
|
||||
|
@ -979,12 +1089,20 @@ class MultiCommand(Command):
|
|||
: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):
|
||||
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
|
||||
|
@ -1004,8 +1122,10 @@ class MultiCommand(Command):
|
|||
if self.chain:
|
||||
for param in self.params:
|
||||
if isinstance(param, Argument) and not param.required:
|
||||
raise RuntimeError('Multi commands in chain mode cannot '
|
||||
'have optional arguments.')
|
||||
raise RuntimeError(
|
||||
"Multi commands in chain mode cannot have"
|
||||
" optional arguments."
|
||||
)
|
||||
|
||||
def collect_usage_pieces(self, ctx):
|
||||
rv = Command.collect_usage_pieces(self, ctx)
|
||||
|
@ -1041,16 +1161,19 @@ class MultiCommand(Command):
|
|||
: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)
|
||||
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):
|
||||
|
@ -1078,7 +1201,7 @@ class MultiCommand(Command):
|
|||
rows.append((subcommand, help))
|
||||
|
||||
if rows:
|
||||
with formatter.section('Commands'):
|
||||
with formatter.section("Commands"):
|
||||
formatter.write_dl(rows)
|
||||
|
||||
def parse_args(self, ctx, args):
|
||||
|
@ -1098,8 +1221,7 @@ class MultiCommand(Command):
|
|||
def invoke(self, ctx):
|
||||
def _process_result(value):
|
||||
if self.result_callback is not None:
|
||||
value = ctx.invoke(self.result_callback, value,
|
||||
**ctx.params)
|
||||
value = ctx.invoke(self.result_callback, value, **ctx.params)
|
||||
return value
|
||||
|
||||
if not ctx.protected_args:
|
||||
|
@ -1115,7 +1237,7 @@ class MultiCommand(Command):
|
|||
with ctx:
|
||||
Command.invoke(self, ctx)
|
||||
return _process_result([])
|
||||
ctx.fail('Missing command.')
|
||||
ctx.fail("Missing command.")
|
||||
|
||||
# Fetch args back out
|
||||
args = ctx.protected_args + ctx.args
|
||||
|
@ -1142,7 +1264,7 @@ class MultiCommand(Command):
|
|||
# set to ``*`` to inform the command that subcommands are executed
|
||||
# but nothing else.
|
||||
with ctx:
|
||||
ctx.invoked_subcommand = args and '*' or None
|
||||
ctx.invoked_subcommand = "*" if args else None
|
||||
Command.invoke(self, ctx)
|
||||
|
||||
# Otherwise we make every single context and invoke them in a
|
||||
|
@ -1151,9 +1273,13 @@ class MultiCommand(Command):
|
|||
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)
|
||||
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 = sub_ctx.args, []
|
||||
|
||||
|
@ -1185,7 +1311,7 @@ class MultiCommand(Command):
|
|||
if cmd is None and not ctx.resilient_parsing:
|
||||
if split_opt(cmd_name)[0]:
|
||||
self.parse_args(ctx, ctx.args)
|
||||
ctx.fail('No such command "%s".' % original_cmd_name)
|
||||
ctx.fail("No such command '{}'.".format(original_cmd_name))
|
||||
|
||||
return cmd_name, cmd, args[1:]
|
||||
|
||||
|
@ -1220,7 +1346,7 @@ class Group(MultiCommand):
|
|||
"""
|
||||
name = name or cmd.name
|
||||
if name is None:
|
||||
raise TypeError('Command has no name.')
|
||||
raise TypeError("Command has no name.")
|
||||
_check_multicommand(self, name, cmd, register=True)
|
||||
self.commands[name] = cmd
|
||||
|
||||
|
@ -1230,10 +1356,13 @@ class Group(MultiCommand):
|
|||
immediately registers the created command with this instance by
|
||||
calling into :meth:`add_command`.
|
||||
"""
|
||||
from .decorators import command
|
||||
|
||||
def decorator(f):
|
||||
cmd = command(*args, **kwargs)(f)
|
||||
self.add_command(cmd)
|
||||
return cmd
|
||||
|
||||
return decorator
|
||||
|
||||
def group(self, *args, **kwargs):
|
||||
|
@ -1242,10 +1371,13 @@ class Group(MultiCommand):
|
|||
immediately registers the created command with this instance by
|
||||
calling into :meth:`add_command`.
|
||||
"""
|
||||
from .decorators import group
|
||||
|
||||
def decorator(f):
|
||||
cmd = group(*args, **kwargs)(f)
|
||||
self.add_command(cmd)
|
||||
return cmd
|
||||
|
||||
return decorator
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
|
@ -1294,12 +1426,6 @@ class Parameter(object):
|
|||
|
||||
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.
|
||||
|
@ -1312,8 +1438,7 @@ class Parameter(object):
|
|||
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)``.
|
||||
value)`` and needs to return the value.
|
||||
:param nargs: the number of arguments to match. If not ``1`` the return
|
||||
value is a tuple instead of single value. The default for
|
||||
nargs is ``1`` (except if the type is a tuple, then it's
|
||||
|
@ -1327,15 +1452,36 @@ class Parameter(object):
|
|||
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=None, metavar=None,
|
||||
expose_value=True, is_eager=False, envvar=None,
|
||||
autocompletion=None):
|
||||
self.name, self.opts, self.secondary_opts = \
|
||||
self._parse_decls(param_decls or (), expose_value)
|
||||
.. versionchanged:: 7.1
|
||||
Empty environment variables are ignored rather than taking the
|
||||
empty string value. This makes it possible for scripts to clear
|
||||
variables if they can't unset them.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Changed signature for parameter callback to also be passed the
|
||||
parameter. The old callback format will still work, but it will
|
||||
raise a warning to give you a chance to migrate the code easier.
|
||||
"""
|
||||
param_type_name = "parameter"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
param_decls=None,
|
||||
type=None,
|
||||
required=False,
|
||||
default=None,
|
||||
callback=None,
|
||||
nargs=None,
|
||||
metavar=None,
|
||||
expose_value=True,
|
||||
is_eager=False,
|
||||
envvar=None,
|
||||
autocompletion=None,
|
||||
):
|
||||
self.name, self.opts, self.secondary_opts = self._parse_decls(
|
||||
param_decls or (), expose_value
|
||||
)
|
||||
|
||||
self.type = convert_type(type, default)
|
||||
|
||||
|
@ -1358,6 +1504,9 @@ class Parameter(object):
|
|||
self.envvar = envvar
|
||||
self.autocompletion = autocompletion
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {}>".format(self.__class__.__name__, self.name)
|
||||
|
||||
@property
|
||||
def human_readable_name(self):
|
||||
"""Returns the human readable name of this parameter. This is the
|
||||
|
@ -1372,7 +1521,7 @@ class Parameter(object):
|
|||
if metavar is None:
|
||||
metavar = self.type.name.upper()
|
||||
if self.nargs != 1:
|
||||
metavar += '...'
|
||||
metavar += "..."
|
||||
return metavar
|
||||
|
||||
def get_default(self, ctx):
|
||||
|
@ -1402,10 +1551,11 @@ class Parameter(object):
|
|||
"""
|
||||
if self.type.is_composite:
|
||||
if self.nargs <= 1:
|
||||
raise TypeError('Attempted to invoke composite type '
|
||||
'but nargs has been set to %s. This is '
|
||||
'not supported; nargs needs to be set to '
|
||||
'a fixed value > 1.' % self.nargs)
|
||||
raise TypeError(
|
||||
"Attempted to invoke composite type but nargs has"
|
||||
" been set to {}. This is not supported; nargs"
|
||||
" needs to be set to a fixed value > 1.".format(self.nargs)
|
||||
)
|
||||
if self.multiple:
|
||||
return tuple(self.type(x or (), self, ctx) for x in value or ())
|
||||
return self.type(value or (), self, ctx)
|
||||
|
@ -1414,6 +1564,7 @@ class Parameter(object):
|
|||
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):
|
||||
|
@ -1454,7 +1605,10 @@ class Parameter(object):
|
|||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
return os.environ.get(self.envvar)
|
||||
rv = os.environ.get(self.envvar)
|
||||
|
||||
if rv != "":
|
||||
return rv
|
||||
|
||||
def value_from_envvar(self, ctx):
|
||||
rv = self.resolve_envvar_value(ctx)
|
||||
|
@ -1473,8 +1627,7 @@ class Parameter(object):
|
|||
value = None
|
||||
if self.callback is not None:
|
||||
try:
|
||||
value = invoke_param_callback(
|
||||
self.callback, ctx, self, value)
|
||||
value = invoke_param_callback(self.callback, ctx, self, value)
|
||||
except Exception:
|
||||
if not ctx.resilient_parsing:
|
||||
raise
|
||||
|
@ -1494,7 +1647,7 @@ class Parameter(object):
|
|||
indicate which param caused the error.
|
||||
"""
|
||||
hint_list = self.opts or [self.human_readable_name]
|
||||
return ' / '.join('"%s"' % x for x in hint_list)
|
||||
return " / ".join(repr(x) for x in hint_list)
|
||||
|
||||
|
||||
class Option(Parameter):
|
||||
|
@ -1535,19 +1688,33 @@ class Option(Parameter):
|
|||
:param help: the help string.
|
||||
:param hidden: hide this option from help outputs.
|
||||
"""
|
||||
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, hidden=False, show_choices=True,
|
||||
show_envvar=False, **attrs):
|
||||
default_is_missing = attrs.get('default', _missing) is _missing
|
||||
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,
|
||||
hidden=False,
|
||||
show_choices=True,
|
||||
show_envvar=False,
|
||||
**attrs
|
||||
):
|
||||
default_is_missing = attrs.get("default", _missing) is _missing
|
||||
Parameter.__init__(self, param_decls, type=type, **attrs)
|
||||
|
||||
if prompt is True:
|
||||
prompt_text = self.name.replace('_', ' ').capitalize()
|
||||
prompt_text = self.name.replace("_", " ").capitalize()
|
||||
elif prompt is False:
|
||||
prompt_text = None
|
||||
else:
|
||||
|
@ -1569,8 +1736,7 @@ class Option(Parameter):
|
|||
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:
|
||||
if self.is_flag and isinstance(self.flag_value, bool) and type in [None, bool]:
|
||||
self.type = BOOL
|
||||
self.is_bool_flag = True
|
||||
else:
|
||||
|
@ -1594,22 +1760,22 @@ class Option(Parameter):
|
|||
# Sanity check for stuff we don't support
|
||||
if __debug__:
|
||||
if self.nargs < 0:
|
||||
raise TypeError('Options cannot have nargs < 0')
|
||||
raise TypeError("Options cannot have nargs < 0")
|
||||
if self.prompt and self.is_flag and not self.is_bool_flag:
|
||||
raise TypeError('Cannot prompt for flags that are not bools.')
|
||||
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.')
|
||||
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.')
|
||||
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.')
|
||||
raise TypeError(
|
||||
"Options cannot be count and flags at the same time."
|
||||
)
|
||||
|
||||
def _parse_decls(self, decls, expose_value):
|
||||
opts = []
|
||||
|
@ -1620,10 +1786,10 @@ class Option(Parameter):
|
|||
for decl in decls:
|
||||
if isidentifier(decl):
|
||||
if name is not None:
|
||||
raise TypeError('Name defined twice')
|
||||
raise TypeError("Name defined twice")
|
||||
name = decl
|
||||
else:
|
||||
split_char = decl[:1] == '/' and ';' or '/'
|
||||
split_char = ";" if decl[:1] == "/" else "/"
|
||||
if split_char in decl:
|
||||
first, second = decl.split(split_char, 1)
|
||||
first = first.rstrip()
|
||||
|
@ -1639,49 +1805,51 @@ class Option(Parameter):
|
|||
|
||||
if name is None and possible_names:
|
||||
possible_names.sort(key=lambda x: -len(x[0])) # group long options first
|
||||
name = possible_names[0][1].replace('-', '_').lower()
|
||||
name = possible_names[0][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')
|
||||
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)
|
||||
raise TypeError(
|
||||
"No options defined but a name was passed ({}). Did you"
|
||||
" mean to declare an argument instead of an option?".format(name)
|
||||
)
|
||||
|
||||
return name, opts, secondary_opts
|
||||
|
||||
def add_to_parser(self, parser, ctx):
|
||||
kwargs = {
|
||||
'dest': self.name,
|
||||
'nargs': self.nargs,
|
||||
'obj': self,
|
||||
"dest": self.name,
|
||||
"nargs": self.nargs,
|
||||
"obj": self,
|
||||
}
|
||||
|
||||
if self.multiple:
|
||||
action = 'append'
|
||||
action = "append"
|
||||
elif self.count:
|
||||
action = 'count'
|
||||
action = "count"
|
||||
else:
|
||||
action = 'store'
|
||||
action = "store"
|
||||
|
||||
if self.is_flag:
|
||||
kwargs.pop('nargs', None)
|
||||
kwargs.pop("nargs", None)
|
||||
action_const = "{}_const".format(action)
|
||||
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)
|
||||
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)
|
||||
parser.add_option(
|
||||
self.opts, action=action_const, const=self.flag_value, **kwargs
|
||||
)
|
||||
else:
|
||||
kwargs['action'] = action
|
||||
kwargs["action"] = action
|
||||
parser.add_option(self.opts, **kwargs)
|
||||
|
||||
def get_help_record(self, ctx):
|
||||
|
@ -1694,46 +1862,50 @@ class Option(Parameter):
|
|||
if any_slashes:
|
||||
any_prefix_is_slash[:] = [True]
|
||||
if not self.is_flag and not self.count:
|
||||
rv += ' ' + self.make_metavar()
|
||||
rv += " {}".format(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 ''
|
||||
help = self.help or ""
|
||||
extra = []
|
||||
if self.show_envvar:
|
||||
envvar = self.envvar
|
||||
if envvar is None:
|
||||
if self.allow_from_autoenv and \
|
||||
ctx.auto_envvar_prefix is not None:
|
||||
envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper())
|
||||
if self.allow_from_autoenv and ctx.auto_envvar_prefix is not None:
|
||||
envvar = "{}_{}".format(ctx.auto_envvar_prefix, self.name.upper())
|
||||
if envvar is not None:
|
||||
extra.append('env var: %s' % (
|
||||
', '.join('%s' % d for d in envvar)
|
||||
if isinstance(envvar, (list, tuple))
|
||||
else envvar, ))
|
||||
if self.default is not None and self.show_default:
|
||||
extra.append(
|
||||
"env var: {}".format(
|
||||
", ".join(str(d) for d in envvar)
|
||||
if isinstance(envvar, (list, tuple))
|
||||
else envvar
|
||||
)
|
||||
)
|
||||
if self.default is not None and (self.show_default or ctx.show_default):
|
||||
if isinstance(self.show_default, string_types):
|
||||
default_string = '({})'.format(self.show_default)
|
||||
default_string = "({})".format(self.show_default)
|
||||
elif isinstance(self.default, (list, tuple)):
|
||||
default_string = ', '.join('%s' % d for d in self.default)
|
||||
default_string = ", ".join(str(d) for d in self.default)
|
||||
elif inspect.isfunction(self.default):
|
||||
default_string = "(dynamic)"
|
||||
else:
|
||||
default_string = self.default
|
||||
extra.append('default: {}'.format(default_string))
|
||||
extra.append("default: {}".format(default_string))
|
||||
|
||||
if self.required:
|
||||
extra.append('required')
|
||||
extra.append("required")
|
||||
if extra:
|
||||
help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra))
|
||||
help = "{}[{}]".format(
|
||||
"{} ".format(help) if help else "", "; ".join(extra)
|
||||
)
|
||||
|
||||
return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help)
|
||||
return ("; " if any_prefix_is_slash else " / ").join(rv), help
|
||||
|
||||
def get_default(self, ctx):
|
||||
# If we're a non boolean flag out default is more complex because
|
||||
# If we're a non boolean flag our default is more complex because
|
||||
# we need to look at all flags in the same group to figure out
|
||||
# if we're the the default one in which case we return the flag
|
||||
# value as default.
|
||||
|
@ -1758,18 +1930,22 @@ class Option(Parameter):
|
|||
if self.is_bool_flag:
|
||||
return confirm(self.prompt, default)
|
||||
|
||||
return prompt(self.prompt, default=default, type=self.type,
|
||||
hide_input=self.hide_input, show_choices=self.show_choices,
|
||||
confirmation_prompt=self.confirmation_prompt,
|
||||
value_proc=lambda x: self.process_value(ctx, x))
|
||||
return prompt(
|
||||
self.prompt,
|
||||
default=default,
|
||||
type=self.type,
|
||||
hide_input=self.hide_input,
|
||||
show_choices=self.show_choices,
|
||||
confirmation_prompt=self.confirmation_prompt,
|
||||
value_proc=lambda x: self.process_value(ctx, x),
|
||||
)
|
||||
|
||||
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())
|
||||
if self.allow_from_autoenv and ctx.auto_envvar_prefix is not None:
|
||||
envvar = "{}_{}".format(ctx.auto_envvar_prefix, self.name.upper())
|
||||
return os.environ.get(envvar)
|
||||
|
||||
def value_from_envvar(self, ctx):
|
||||
|
@ -1784,8 +1960,7 @@ class Option(Parameter):
|
|||
return rv
|
||||
|
||||
def full_process_value(self, ctx, value):
|
||||
if value is None and self.prompt is not None \
|
||||
and not ctx.resilient_parsing:
|
||||
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)
|
||||
|
||||
|
@ -1797,18 +1972,20 @@ class Argument(Parameter):
|
|||
|
||||
All parameters are passed onwards to the parameter constructor.
|
||||
"""
|
||||
param_type_name = 'argument'
|
||||
|
||||
param_type_name = "argument"
|
||||
|
||||
def __init__(self, param_decls, required=None, **attrs):
|
||||
if required is None:
|
||||
if attrs.get('default') is not None:
|
||||
if attrs.get("default") is not None:
|
||||
required = False
|
||||
else:
|
||||
required = attrs.get('nargs', 1) > 0
|
||||
required = attrs.get("nargs", 1) > 0
|
||||
Parameter.__init__(self, param_decls, required=required, **attrs)
|
||||
if self.default is not None and self.nargs < 0:
|
||||
raise TypeError('nargs=-1 in combination with a default value '
|
||||
'is not supported.')
|
||||
raise TypeError(
|
||||
"nargs=-1 in combination with a default value is not supported."
|
||||
)
|
||||
|
||||
@property
|
||||
def human_readable_name(self):
|
||||
|
@ -1823,34 +2000,31 @@ class Argument(Parameter):
|
|||
if not var:
|
||||
var = self.name.upper()
|
||||
if not self.required:
|
||||
var = '[%s]' % var
|
||||
var = "[{}]".format(var)
|
||||
if self.nargs != 1:
|
||||
var += '...'
|
||||
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')
|
||||
raise TypeError("Could not determine name for argument")
|
||||
if len(decls) == 1:
|
||||
name = arg = decls[0]
|
||||
name = name.replace('-', '_').lower()
|
||||
name = name.replace("-", "_").lower()
|
||||
else:
|
||||
raise TypeError('Arguments take exactly one '
|
||||
'parameter declaration, got %d' % len(decls))
|
||||
raise TypeError(
|
||||
"Arguments take exactly one parameter declaration, got"
|
||||
" {}".format(len(decls))
|
||||
)
|
||||
return name, [arg], []
|
||||
|
||||
def get_usage_pieces(self, ctx):
|
||||
return [self.make_metavar()]
|
||||
|
||||
def get_error_hint(self, ctx):
|
||||
return '"%s"' % self.make_metavar()
|
||||
return repr(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
|
||||
parser.add_argument(dest=self.name, nargs=self.nargs, obj=self)
|
|
@ -1,20 +1,25 @@
|
|||
import sys
|
||||
import inspect
|
||||
|
||||
import sys
|
||||
from functools import update_wrapper
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._unicodefun import _check_for_unicode_literals
|
||||
from .utils import echo
|
||||
from .core import Argument
|
||||
from .core import Command
|
||||
from .core import Group
|
||||
from .core import Option
|
||||
from .globals import get_current_context
|
||||
from .utils import echo
|
||||
|
||||
|
||||
def pass_context(f):
|
||||
"""Marks a callback as wanting to receive the current context
|
||||
object as first argument.
|
||||
"""
|
||||
|
||||
def new_func(*args, **kwargs):
|
||||
return f(get_current_context(), *args, **kwargs)
|
||||
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
|
@ -23,8 +28,10 @@ def pass_obj(f):
|
|||
context onwards (:attr:`Context.obj`). This is useful if that object
|
||||
represents the state of a nested system.
|
||||
"""
|
||||
|
||||
def new_func(*args, **kwargs):
|
||||
return f(get_current_context().obj, *args, **kwargs)
|
||||
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
|
@ -50,6 +57,7 @@ def make_pass_decorator(object_type, ensure=False):
|
|||
: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):
|
||||
def new_func(*args, **kwargs):
|
||||
ctx = get_current_context()
|
||||
|
@ -58,35 +66,41 @@ def make_pass_decorator(object_type, ensure=False):
|
|||
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__)
|
||||
raise RuntimeError(
|
||||
"Managed to invoke callback without a context"
|
||||
" object of type '{}' existing".format(object_type.__name__)
|
||||
)
|
||||
return ctx.invoke(f, obj, *args, **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.')
|
||||
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')
|
||||
help = attrs.get("help")
|
||||
if help is None:
|
||||
help = inspect.getdoc(f)
|
||||
if isinstance(help, bytes):
|
||||
help = help.decode('utf-8')
|
||||
help = help.decode("utf-8")
|
||||
else:
|
||||
help = inspect.cleandoc(help)
|
||||
attrs['help'] = help
|
||||
attrs["help"] = help
|
||||
_check_for_unicode_literals()
|
||||
return cls(name=name or f.__name__.lower().replace('_', '-'),
|
||||
callback=f, params=params, **attrs)
|
||||
return cls(
|
||||
name=name or f.__name__.lower().replace("_", "-"),
|
||||
callback=f,
|
||||
params=params,
|
||||
**attrs
|
||||
)
|
||||
|
||||
|
||||
def command(name=None, cls=None, **attrs):
|
||||
|
@ -94,9 +108,9 @@ def command(name=None, cls=None, **attrs):
|
|||
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.
|
||||
The name of the command defaults to the name of the function with
|
||||
underscores replaced by dashes. 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.
|
||||
|
||||
|
@ -111,10 +125,12 @@ def command(name=None, cls=None, **attrs):
|
|||
"""
|
||||
if cls is None:
|
||||
cls = Command
|
||||
|
||||
def decorator(f):
|
||||
cmd = _make_command(f, name, attrs, cls)
|
||||
cmd.__doc__ = f.__doc__
|
||||
return cmd
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
|
@ -123,7 +139,7 @@ def group(name=None, **attrs):
|
|||
works otherwise the same as :func:`command` just that the `cls`
|
||||
parameter is set to :class:`Group`.
|
||||
"""
|
||||
attrs.setdefault('cls', Group)
|
||||
attrs.setdefault("cls", Group)
|
||||
return command(name, **attrs)
|
||||
|
||||
|
||||
|
@ -131,7 +147,7 @@ def _param_memo(f, param):
|
|||
if isinstance(f, Command):
|
||||
f.params.append(param)
|
||||
else:
|
||||
if not hasattr(f, '__click_params__'):
|
||||
if not hasattr(f, "__click_params__"):
|
||||
f.__click_params__ = []
|
||||
f.__click_params__.append(param)
|
||||
|
||||
|
@ -146,10 +162,12 @@ def argument(*param_decls, **attrs):
|
|||
:param cls: the argument class to instantiate. This defaults to
|
||||
:class:`Argument`.
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
ArgumentClass = attrs.pop('cls', Argument)
|
||||
ArgumentClass = attrs.pop("cls", Argument)
|
||||
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
|
@ -163,15 +181,17 @@ def option(*param_decls, **attrs):
|
|||
:param cls: the option class to instantiate. This defaults to
|
||||
:class:`Option`.
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||
option_attrs = attrs.copy()
|
||||
|
||||
if 'help' in option_attrs:
|
||||
option_attrs['help'] = inspect.cleandoc(option_attrs['help'])
|
||||
OptionClass = option_attrs.pop('cls', Option)
|
||||
if "help" in option_attrs:
|
||||
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
|
||||
OptionClass = option_attrs.pop("cls", Option)
|
||||
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
|
@ -192,16 +212,19 @@ def confirmation_option(*param_decls, **attrs):
|
|||
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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -217,11 +240,13 @@ def password_option(*param_decls, **attrs):
|
|||
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)
|
||||
attrs.setdefault("prompt", True)
|
||||
attrs.setdefault("confirmation_prompt", True)
|
||||
attrs.setdefault("hide_input", True)
|
||||
return option(*(param_decls or ("--password",)), **attrs)(f)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
|
@ -238,14 +263,14 @@ def version_option(version=None, *param_decls, **attrs):
|
|||
:param others: everything else is forwarded to :func:`option`.
|
||||
"""
|
||||
if version is None:
|
||||
if hasattr(sys, '_getframe'):
|
||||
module = sys._getframe(1).f_globals.get('__name__')
|
||||
if hasattr(sys, "_getframe"):
|
||||
module = sys._getframe(1).f_globals.get("__name__")
|
||||
else:
|
||||
module = ''
|
||||
module = ""
|
||||
|
||||
def decorator(f):
|
||||
prog_name = attrs.pop('prog_name', None)
|
||||
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
||||
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:
|
||||
|
@ -261,25 +286,23 @@ def version_option(version=None, *param_decls, **attrs):
|
|||
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):
|
||||
scripts = dist.get_entry_map().get("console_scripts") or {}
|
||||
for _, 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,
|
||||
}, color=ctx.color)
|
||||
raise RuntimeError("Could not determine version")
|
||||
echo(message % {"prog": prog, "version": ver}, color=ctx.color)
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
|
@ -293,19 +316,18 @@ def help_option(*param_decls, **attrs):
|
|||
|
||||
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(), color=ctx.color)
|
||||
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)
|
||||
|
||||
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
|
|
@ -1,10 +1,12 @@
|
|||
from ._compat import PY2, filename_to_ui, get_text_stderr
|
||||
from ._compat import filename_to_ui
|
||||
from ._compat import get_text_stderr
|
||||
from ._compat import PY2
|
||||
from .utils import echo
|
||||
|
||||
|
||||
def _join_param_hints(param_hint):
|
||||
if isinstance(param_hint, (tuple, list)):
|
||||
return ' / '.join('"%s"' % x for x in param_hint)
|
||||
return " / ".join(repr(x) for x in param_hint)
|
||||
return param_hint
|
||||
|
||||
|
||||
|
@ -18,7 +20,7 @@ class ClickException(Exception):
|
|||
ctor_msg = message
|
||||
if PY2:
|
||||
if ctor_msg is not None:
|
||||
ctor_msg = ctor_msg.encode('utf-8')
|
||||
ctor_msg = ctor_msg.encode("utf-8")
|
||||
Exception.__init__(self, ctor_msg)
|
||||
self.message = message
|
||||
|
||||
|
@ -32,12 +34,12 @@ class ClickException(Exception):
|
|||
__unicode__ = __str__
|
||||
|
||||
def __str__(self):
|
||||
return self.message.encode('utf-8')
|
||||
return self.message.encode("utf-8")
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
echo('Error: %s' % self.format_message(), file=file)
|
||||
echo("Error: {}".format(self.format_message()), file=file)
|
||||
|
||||
|
||||
class UsageError(ClickException):
|
||||
|
@ -48,26 +50,27 @@ class UsageError(ClickException):
|
|||
: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
|
||||
self.cmd = self.ctx and self.ctx.command or None
|
||||
self.cmd = self.ctx.command if self.ctx else None
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
color = None
|
||||
hint = ''
|
||||
if (self.cmd is not None and
|
||||
self.cmd.get_help_option(self.ctx) is not None):
|
||||
hint = ('Try "%s %s" for help.\n'
|
||||
% (self.ctx.command_path, self.ctx.help_option_names[0]))
|
||||
hint = ""
|
||||
if self.cmd is not None and self.cmd.get_help_option(self.ctx) is not None:
|
||||
hint = "Try '{} {}' for help.\n".format(
|
||||
self.ctx.command_path, self.ctx.help_option_names[0]
|
||||
)
|
||||
if self.ctx is not None:
|
||||
color = self.ctx.color
|
||||
echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color)
|
||||
echo('Error: %s' % self.format_message(), file=file, color=color)
|
||||
echo("{}\n{}".format(self.ctx.get_usage(), hint), file=file, color=color)
|
||||
echo("Error: {}".format(self.format_message()), file=file, color=color)
|
||||
|
||||
|
||||
class BadParameter(UsageError):
|
||||
|
@ -88,8 +91,7 @@ class BadParameter(UsageError):
|
|||
each item is quoted and separated.
|
||||
"""
|
||||
|
||||
def __init__(self, message, ctx=None, param=None,
|
||||
param_hint=None):
|
||||
def __init__(self, message, ctx=None, param=None, param_hint=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
self.param = param
|
||||
self.param_hint = param_hint
|
||||
|
@ -100,10 +102,10 @@ class BadParameter(UsageError):
|
|||
elif self.param is not None:
|
||||
param_hint = self.param.get_error_hint(self.ctx)
|
||||
else:
|
||||
return 'Invalid value: %s' % self.message
|
||||
return "Invalid value: {}".format(self.message)
|
||||
param_hint = _join_param_hints(param_hint)
|
||||
|
||||
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
||||
return "Invalid value for {}: {}".format(param_hint, self.message)
|
||||
|
||||
|
||||
class MissingParameter(BadParameter):
|
||||
|
@ -118,8 +120,9 @@ class MissingParameter(BadParameter):
|
|||
``'option'`` or ``'argument'``.
|
||||
"""
|
||||
|
||||
def __init__(self, message=None, ctx=None, param=None,
|
||||
param_hint=None, param_type=None):
|
||||
def __init__(
|
||||
self, message=None, ctx=None, param=None, param_hint=None, param_type=None
|
||||
):
|
||||
BadParameter.__init__(self, message, ctx, param, param_hint)
|
||||
self.param_type = param_type
|
||||
|
||||
|
@ -141,17 +144,30 @@ class MissingParameter(BadParameter):
|
|||
msg_extra = self.param.type.get_missing_message(self.param)
|
||||
if msg_extra:
|
||||
if msg:
|
||||
msg += '. ' + msg_extra
|
||||
msg += ". {}".format(msg_extra)
|
||||
else:
|
||||
msg = msg_extra
|
||||
|
||||
return 'Missing %s%s%s%s' % (
|
||||
return "Missing {}{}{}{}".format(
|
||||
param_type,
|
||||
param_hint and ' %s' % param_hint or '',
|
||||
msg and '. ' or '.',
|
||||
msg or '',
|
||||
" {}".format(param_hint) if param_hint else "",
|
||||
". " if msg else ".",
|
||||
msg or "",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
if self.message is None:
|
||||
param_name = self.param.name if self.param else None
|
||||
return "missing parameter: {}".format(param_name)
|
||||
else:
|
||||
return self.message
|
||||
|
||||
if PY2:
|
||||
__unicode__ = __str__
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__().encode("utf-8")
|
||||
|
||||
|
||||
class NoSuchOption(UsageError):
|
||||
"""Raised if click attempted to handle an option that does not
|
||||
|
@ -160,10 +176,9 @@ class NoSuchOption(UsageError):
|
|||
.. versionadded:: 4.0
|
||||
"""
|
||||
|
||||
def __init__(self, option_name, message=None, possibilities=None,
|
||||
ctx=None):
|
||||
def __init__(self, option_name, message=None, possibilities=None, ctx=None):
|
||||
if message is None:
|
||||
message = 'no such option: %s' % option_name
|
||||
message = "no such option: {}".format(option_name)
|
||||
UsageError.__init__(self, message, ctx)
|
||||
self.option_name = option_name
|
||||
self.possibilities = possibilities
|
||||
|
@ -172,11 +187,11 @@ class NoSuchOption(UsageError):
|
|||
bits = [self.message]
|
||||
if self.possibilities:
|
||||
if len(self.possibilities) == 1:
|
||||
bits.append('Did you mean %s?' % self.possibilities[0])
|
||||
bits.append("Did you mean {}?".format(self.possibilities[0]))
|
||||
else:
|
||||
possibilities = sorted(self.possibilities)
|
||||
bits.append('(Possible options: %s)' % ', '.join(possibilities))
|
||||
return ' '.join(bits)
|
||||
bits.append("(Possible options: {})".format(", ".join(possibilities)))
|
||||
return " ".join(bits)
|
||||
|
||||
|
||||
class BadOptionUsage(UsageError):
|
||||
|
@ -212,13 +227,13 @@ class FileError(ClickException):
|
|||
def __init__(self, filename, hint=None):
|
||||
ui_filename = filename_to_ui(filename)
|
||||
if hint is None:
|
||||
hint = 'unknown error'
|
||||
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)
|
||||
return "Could not open file {}: {}".format(self.ui_filename, self.message)
|
||||
|
||||
|
||||
class Abort(RuntimeError):
|
||||
|
@ -231,5 +246,8 @@ class Exit(RuntimeError):
|
|||
|
||||
:param code: the status code to exit with.
|
||||
"""
|
||||
|
||||
__slots__ = ("exit_code",)
|
||||
|
||||
def __init__(self, code=0):
|
||||
self.exit_code = code
|
|
@ -1,8 +1,8 @@
|
|||
from contextlib import contextmanager
|
||||
from .termui import get_terminal_size
|
||||
from .parser import split_opt
|
||||
from ._compat import term_len
|
||||
|
||||
from ._compat import term_len
|
||||
from .parser import split_opt
|
||||
from .termui import get_terminal_size
|
||||
|
||||
# Can force a width. This is used by the test system
|
||||
FORCED_WIDTH = None
|
||||
|
@ -19,11 +19,12 @@ def measure_table(rows):
|
|||
def iter_rows(rows, col_count):
|
||||
for row in rows:
|
||||
row = tuple(row)
|
||||
yield row + ('',) * (col_count - len(row))
|
||||
yield row + ("",) * (col_count - len(row))
|
||||
|
||||
|
||||
def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
||||
preserve_paragraphs=False):
|
||||
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
|
||||
|
@ -43,10 +44,14 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
|||
intelligently handle paragraphs.
|
||||
"""
|
||||
from ._textwrap import TextWrapper
|
||||
|
||||
text = text.expandtabs()
|
||||
wrapper = TextWrapper(width, initial_indent=initial_indent,
|
||||
subsequent_indent=subsequent_indent,
|
||||
replace_whitespace=False)
|
||||
wrapper = TextWrapper(
|
||||
width,
|
||||
initial_indent=initial_indent,
|
||||
subsequent_indent=subsequent_indent,
|
||||
replace_whitespace=False,
|
||||
)
|
||||
if not preserve_paragraphs:
|
||||
return wrapper.fill(text)
|
||||
|
||||
|
@ -57,10 +62,10 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
|||
def _flush_par():
|
||||
if not buf:
|
||||
return
|
||||
if buf[0].strip() == '\b':
|
||||
p.append((indent or 0, True, '\n'.join(buf[1:])))
|
||||
if buf[0].strip() == "\b":
|
||||
p.append((indent or 0, True, "\n".join(buf[1:])))
|
||||
else:
|
||||
p.append((indent or 0, False, ' '.join(buf)))
|
||||
p.append((indent or 0, False, " ".join(buf)))
|
||||
del buf[:]
|
||||
|
||||
for line in text.splitlines():
|
||||
|
@ -77,13 +82,13 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
|||
|
||||
rv = []
|
||||
for indent, raw, text in p:
|
||||
with wrapper.extra_indent(' ' * indent):
|
||||
with wrapper.extra_indent(" " * indent):
|
||||
if raw:
|
||||
rv.append(wrapper.indent_only(text))
|
||||
else:
|
||||
rv.append(wrapper.fill(text))
|
||||
|
||||
return '\n\n'.join(rv)
|
||||
return "\n\n".join(rv)
|
||||
|
||||
|
||||
class HelpFormatter(object):
|
||||
|
@ -122,53 +127,65 @@ class HelpFormatter(object):
|
|||
"""Decreases the indentation."""
|
||||
self.current_indent -= self.indent_increment
|
||||
|
||||
def write_usage(self, prog, args='', prefix='Usage: '):
|
||||
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.
|
||||
"""
|
||||
usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog)
|
||||
usage_prefix = "{:>{w}}{} ".format(prefix, prog, w=self.current_indent)
|
||||
text_width = self.width - self.current_indent
|
||||
|
||||
if text_width >= (term_len(usage_prefix) + 20):
|
||||
# The arguments will fit to the right of the prefix.
|
||||
indent = ' ' * term_len(usage_prefix)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=usage_prefix,
|
||||
subsequent_indent=indent))
|
||||
indent = " " * term_len(usage_prefix)
|
||||
self.write(
|
||||
wrap_text(
|
||||
args,
|
||||
text_width,
|
||||
initial_indent=usage_prefix,
|
||||
subsequent_indent=indent,
|
||||
)
|
||||
)
|
||||
else:
|
||||
# The prefix is too long, put the arguments on the next line.
|
||||
self.write(usage_prefix)
|
||||
self.write('\n')
|
||||
indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=indent,
|
||||
subsequent_indent=indent))
|
||||
self.write("\n")
|
||||
indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
|
||||
self.write(
|
||||
wrap_text(
|
||||
args, text_width, initial_indent=indent, subsequent_indent=indent
|
||||
)
|
||||
)
|
||||
|
||||
self.write('\n')
|
||||
self.write("\n")
|
||||
|
||||
def write_heading(self, heading):
|
||||
"""Writes a heading into the buffer."""
|
||||
self.write('%*s%s:\n' % (self.current_indent, '', heading))
|
||||
self.write("{:>{w}}{}:\n".format("", heading, w=self.current_indent))
|
||||
|
||||
def write_paragraph(self):
|
||||
"""Writes a paragraph into the buffer."""
|
||||
if self.buffer:
|
||||
self.write('\n')
|
||||
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')
|
||||
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
|
||||
|
@ -182,30 +199,40 @@ class HelpFormatter(object):
|
|||
rows = list(rows)
|
||||
widths = measure_table(rows)
|
||||
if len(widths) != 2:
|
||||
raise TypeError('Expected two columns for definition list')
|
||||
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))
|
||||
self.write("{:>{w}}{}".format("", first, w=self.current_indent))
|
||||
if not second:
|
||||
self.write('\n')
|
||||
self.write("\n")
|
||||
continue
|
||||
if term_len(first) <= first_col - col_spacing:
|
||||
self.write(' ' * (first_col - term_len(first)))
|
||||
self.write(" " * (first_col - term_len(first)))
|
||||
else:
|
||||
self.write('\n')
|
||||
self.write(' ' * (first_col + self.current_indent))
|
||||
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())
|
||||
wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
|
||||
lines = wrapped_text.splitlines()
|
||||
|
||||
if lines:
|
||||
self.write(next(lines) + '\n')
|
||||
for line in lines:
|
||||
self.write('%*s%s\n' % (
|
||||
first_col + self.current_indent, '', line))
|
||||
self.write("{}\n".format(lines[0]))
|
||||
|
||||
for line in lines[1:]:
|
||||
self.write(
|
||||
"{:>{w}}{}\n".format(
|
||||
"", line, w=first_col + self.current_indent
|
||||
)
|
||||
)
|
||||
|
||||
if len(lines) > 1:
|
||||
# separate long help from next option
|
||||
self.write("\n")
|
||||
else:
|
||||
self.write('\n')
|
||||
self.write("\n")
|
||||
|
||||
@contextmanager
|
||||
def section(self, name):
|
||||
|
@ -233,7 +260,7 @@ class HelpFormatter(object):
|
|||
|
||||
def getvalue(self):
|
||||
"""Returns the buffer contents."""
|
||||
return ''.join(self.buffer)
|
||||
return "".join(self.buffer)
|
||||
|
||||
|
||||
def join_options(options):
|
||||
|
@ -246,11 +273,11 @@ def join_options(options):
|
|||
any_prefix_is_slash = False
|
||||
for opt in options:
|
||||
prefix = split_opt(opt)[0]
|
||||
if prefix == '/':
|
||||
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)
|
||||
rv = ", ".join(x[1] for x in rv)
|
||||
return rv, any_prefix_is_slash
|
|
@ -1,6 +1,5 @@
|
|||
from threading import local
|
||||
|
||||
|
||||
_local = local()
|
||||
|
||||
|
||||
|
@ -15,20 +14,20 @@ def get_current_context(silent=False):
|
|||
|
||||
.. versionadded:: 5.0
|
||||
|
||||
:param silent: is set to `True` the return value is `None` if no context
|
||||
:param silent: if set to `True` the return value is `None` if no context
|
||||
is available. The default behavior is to raise a
|
||||
:exc:`RuntimeError`.
|
||||
"""
|
||||
try:
|
||||
return getattr(_local, 'stack')[-1]
|
||||
return _local.stack[-1]
|
||||
except (AttributeError, IndexError):
|
||||
if not silent:
|
||||
raise RuntimeError('There is no active click context.')
|
||||
raise RuntimeError("There is no active click context.")
|
||||
|
||||
|
||||
def push_context(ctx):
|
||||
"""Pushes a new context to the current stack."""
|
||||
_local.__dict__.setdefault('stack', []).append(ctx)
|
||||
_local.__dict__.setdefault("stack", []).append(ctx)
|
||||
|
||||
|
||||
def pop_context():
|
|
@ -1,8 +1,5 @@
|
|||
# -*- 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
|
||||
|
@ -14,12 +11,20 @@ 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.
|
||||
"""
|
||||
|
||||
Click uses parts of optparse written by Gregory P. Ward and maintained
|
||||
by the Python Software Foundation. This is limited to code in parser.py.
|
||||
|
||||
Copyright 2001-2006 Gregory P. Ward. All rights reserved.
|
||||
Copyright 2002-2006 Python Software Foundation. All rights reserved.
|
||||
"""
|
||||
import re
|
||||
from collections import deque
|
||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
||||
BadArgumentUsage
|
||||
|
||||
from .exceptions import BadArgumentUsage
|
||||
from .exceptions import BadOptionUsage
|
||||
from .exceptions import NoSuchOption
|
||||
from .exceptions import UsageError
|
||||
|
||||
|
||||
def _unpack_args(args, nargs_spec):
|
||||
|
@ -59,7 +64,7 @@ def _unpack_args(args, nargs_spec):
|
|||
rv.append(tuple(x))
|
||||
elif nargs < 0:
|
||||
if spos is not None:
|
||||
raise TypeError('Cannot have two nargs < 0')
|
||||
raise TypeError("Cannot have two nargs < 0")
|
||||
spos = len(rv)
|
||||
rv.append(None)
|
||||
|
||||
|
@ -68,21 +73,21 @@ def _unpack_args(args, nargs_spec):
|
|||
if spos is not None:
|
||||
rv[spos] = tuple(args)
|
||||
args = []
|
||||
rv[spos + 1:] = reversed(rv[spos + 1:])
|
||||
rv[spos + 1 :] = reversed(rv[spos + 1 :])
|
||||
|
||||
return tuple(rv), list(args)
|
||||
|
||||
|
||||
def _error_opt_args(nargs, opt):
|
||||
if nargs == 1:
|
||||
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
|
||||
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
|
||||
raise BadOptionUsage(opt, "{} option requires an argument".format(opt))
|
||||
raise BadOptionUsage(opt, "{} option requires {} arguments".format(opt, nargs))
|
||||
|
||||
|
||||
def split_opt(opt):
|
||||
first = opt[:1]
|
||||
if first.isalnum():
|
||||
return '', opt
|
||||
return "", opt
|
||||
if opt[1:2] == first:
|
||||
return opt[:2], opt[2:]
|
||||
return first, opt[1:]
|
||||
|
@ -98,13 +103,14 @@ def normalize_opt(opt, ctx):
|
|||
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):
|
||||
for match in re.finditer(
|
||||
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')
|
||||
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:
|
||||
|
@ -114,7 +120,6 @@ def split_arg_string(string):
|
|||
|
||||
|
||||
class Option(object):
|
||||
|
||||
def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None):
|
||||
self._short_opts = []
|
||||
self._long_opts = []
|
||||
|
@ -123,8 +128,7 @@ class Option(object):
|
|||
for opt in opts:
|
||||
prefix, value = split_opt(opt)
|
||||
if not prefix:
|
||||
raise ValueError('Invalid start character for option (%s)'
|
||||
% opt)
|
||||
raise ValueError("Invalid start character for option ({})".format(opt))
|
||||
self.prefixes.add(prefix[0])
|
||||
if len(prefix) == 1 and len(value) == 1:
|
||||
self._short_opts.append(opt)
|
||||
|
@ -133,7 +137,7 @@ class Option(object):
|
|||
self.prefixes.add(prefix)
|
||||
|
||||
if action is None:
|
||||
action = 'store'
|
||||
action = "store"
|
||||
|
||||
self.dest = dest
|
||||
self.action = action
|
||||
|
@ -143,26 +147,25 @@ class Option(object):
|
|||
|
||||
@property
|
||||
def takes_value(self):
|
||||
return self.action in ('store', 'append')
|
||||
return self.action in ("store", "append")
|
||||
|
||||
def process(self, value, state):
|
||||
if self.action == 'store':
|
||||
if self.action == "store":
|
||||
state.opts[self.dest] = value
|
||||
elif self.action == 'store_const':
|
||||
elif self.action == "store_const":
|
||||
state.opts[self.dest] = self.const
|
||||
elif self.action == 'append':
|
||||
elif self.action == "append":
|
||||
state.opts.setdefault(self.dest, []).append(value)
|
||||
elif self.action == 'append_const':
|
||||
elif self.action == "append_const":
|
||||
state.opts.setdefault(self.dest, []).append(self.const)
|
||||
elif self.action == 'count':
|
||||
elif self.action == "count":
|
||||
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1
|
||||
else:
|
||||
raise ValueError('unknown action %r' % self.action)
|
||||
raise ValueError("unknown action '{}'".format(self.action))
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class Argument(object):
|
||||
|
||||
def __init__(self, dest, nargs=1, obj=None):
|
||||
self.dest = dest
|
||||
self.nargs = nargs
|
||||
|
@ -174,14 +177,14 @@ class Argument(object):
|
|||
if holes == len(value):
|
||||
value = None
|
||||
elif holes != 0:
|
||||
raise BadArgumentUsage('argument %s takes %d values'
|
||||
% (self.dest, self.nargs))
|
||||
raise BadArgumentUsage(
|
||||
"argument {} takes {} values".format(self.dest, self.nargs)
|
||||
)
|
||||
state.opts[self.dest] = value
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class ParsingState(object):
|
||||
|
||||
def __init__(self, rargs):
|
||||
self.opts = {}
|
||||
self.largs = []
|
||||
|
@ -222,11 +225,10 @@ class OptionParser(object):
|
|||
self.ignore_unknown_options = ctx.ignore_unknown_options
|
||||
self._short_opt = {}
|
||||
self._long_opt = {}
|
||||
self._opt_prefixes = set(['-', '--'])
|
||||
self._opt_prefixes = {"-", "--"}
|
||||
self._args = []
|
||||
|
||||
def add_option(self, opts, dest, action=None, nargs=1, const=None,
|
||||
obj=None):
|
||||
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``,
|
||||
|
@ -238,8 +240,7 @@ class OptionParser(object):
|
|||
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)
|
||||
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
|
||||
|
@ -273,8 +274,9 @@ class OptionParser(object):
|
|||
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])
|
||||
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)
|
||||
|
@ -288,7 +290,7 @@ class OptionParser(object):
|
|||
arglen = len(arg)
|
||||
# Double dashes always handled explicitly regardless of what
|
||||
# prefixes are valid.
|
||||
if arg == '--':
|
||||
if arg == "--":
|
||||
return
|
||||
elif arg[:1] in self._opt_prefixes and arglen > 1:
|
||||
self._process_opts(arg, state)
|
||||
|
@ -320,8 +322,7 @@ class OptionParser(object):
|
|||
|
||||
def _match_long_opt(self, opt, explicit_value, state):
|
||||
if opt not in self._long_opt:
|
||||
possibilities = [word for word in self._long_opt
|
||||
if word.startswith(opt)]
|
||||
possibilities = [word for word in self._long_opt if word.startswith(opt)]
|
||||
raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
|
||||
|
||||
option = self._long_opt[opt]
|
||||
|
@ -343,7 +344,7 @@ class OptionParser(object):
|
|||
del state.rargs[:nargs]
|
||||
|
||||
elif explicit_value is not None:
|
||||
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
|
||||
raise BadOptionUsage(opt, "{} option does not take a value".format(opt))
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
@ -395,15 +396,15 @@ class OptionParser(object):
|
|||
# to the state as new larg. This way there is basic combinatorics
|
||||
# that can be achieved while still ignoring unknown arguments.
|
||||
if self.ignore_unknown_options and unknown_options:
|
||||
state.largs.append(prefix + ''.join(unknown_options))
|
||||
state.largs.append("{}{}".format(prefix, "".join(unknown_options)))
|
||||
|
||||
def _process_opts(self, arg, state):
|
||||
explicit_value = None
|
||||
# Long option handling happens in two parts. The first part is
|
||||
# supporting explicitly attached values. In any case, we will try
|
||||
# to long match the option first.
|
||||
if '=' in arg:
|
||||
long_opt, explicit_value = arg.split('=', 1)
|
||||
if "=" in arg:
|
||||
long_opt, explicit_value = arg.split("=", 1)
|
||||
else:
|
||||
long_opt = arg
|
||||
norm_long_opt = normalize_opt(long_opt, self.ctx)
|
|
@ -1,60 +1,89 @@
|
|||
import os
|
||||
import sys
|
||||
import struct
|
||||
import inspect
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from ._compat import raw_input, text_type, string_types, \
|
||||
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
||||
from .utils import echo
|
||||
from .exceptions import Abort, UsageError
|
||||
from .types import convert_type, Choice, Path
|
||||
from ._compat import DEFAULT_COLUMNS
|
||||
from ._compat import get_winterm_size
|
||||
from ._compat import isatty
|
||||
from ._compat import raw_input
|
||||
from ._compat import string_types
|
||||
from ._compat import strip_ansi
|
||||
from ._compat import text_type
|
||||
from ._compat import WIN
|
||||
from .exceptions import Abort
|
||||
from .exceptions import UsageError
|
||||
from .globals import resolve_color_default
|
||||
|
||||
from .types import Choice
|
||||
from .types import convert_type
|
||||
from .types import Path
|
||||
from .utils import echo
|
||||
from .utils import LazyFile
|
||||
|
||||
# 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': 30,
|
||||
'red': 31,
|
||||
'green': 32,
|
||||
'yellow': 33,
|
||||
'blue': 34,
|
||||
'magenta': 35,
|
||||
'cyan': 36,
|
||||
'white': 37,
|
||||
'reset': 39,
|
||||
'bright_black': 90,
|
||||
'bright_red': 91,
|
||||
'bright_green': 92,
|
||||
'bright_yellow': 93,
|
||||
'bright_blue': 94,
|
||||
'bright_magenta': 95,
|
||||
'bright_cyan': 96,
|
||||
'bright_white': 97,
|
||||
"black": 30,
|
||||
"red": 31,
|
||||
"green": 32,
|
||||
"yellow": 33,
|
||||
"blue": 34,
|
||||
"magenta": 35,
|
||||
"cyan": 36,
|
||||
"white": 37,
|
||||
"reset": 39,
|
||||
"bright_black": 90,
|
||||
"bright_red": 91,
|
||||
"bright_green": 92,
|
||||
"bright_yellow": 93,
|
||||
"bright_blue": 94,
|
||||
"bright_magenta": 95,
|
||||
"bright_cyan": 96,
|
||||
"bright_white": 97,
|
||||
}
|
||||
_ansi_reset_all = '\033[0m'
|
||||
_ansi_reset_all = "\033[0m"
|
||||
|
||||
|
||||
def hidden_prompt_func(prompt):
|
||||
import getpass
|
||||
|
||||
return getpass.getpass(prompt)
|
||||
|
||||
|
||||
def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None):
|
||||
def _build_prompt(
|
||||
text, suffix, show_default=False, default=None, show_choices=True, type=None
|
||||
):
|
||||
prompt = text
|
||||
if type is not None and show_choices and isinstance(type, Choice):
|
||||
prompt += ' (' + ", ".join(map(str, type.choices)) + ')'
|
||||
prompt += " ({})".format(", ".join(map(str, type.choices)))
|
||||
if default is not None and show_default:
|
||||
prompt = '%s [%s]' % (prompt, default)
|
||||
prompt = "{} [{}]".format(prompt, _format_default(default))
|
||||
return prompt + suffix
|
||||
|
||||
|
||||
def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
||||
type=None, value_proc=None, prompt_suffix=': ', show_default=True,
|
||||
err=False, show_choices=True):
|
||||
def _format_default(default):
|
||||
if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"):
|
||||
return default.name
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def prompt(
|
||||
text,
|
||||
default=None,
|
||||
hide_input=False,
|
||||
confirmation_prompt=False,
|
||||
type=None,
|
||||
value_proc=None,
|
||||
prompt_suffix=": ",
|
||||
show_default=True,
|
||||
err=False,
|
||||
show_choices=True,
|
||||
):
|
||||
"""Prompts a user for input. This is a convenience function that can
|
||||
be used to prompt a user for input later.
|
||||
|
||||
|
@ -92,12 +121,12 @@ def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
|||
result = None
|
||||
|
||||
def prompt_func(text):
|
||||
f = hide_input and hidden_prompt_func or visible_prompt_func
|
||||
f = hidden_prompt_func if hide_input else visible_prompt_func
|
||||
try:
|
||||
# Write the prompt separately so that we get nice
|
||||
# coloring through colorama on Windows
|
||||
echo(text, nl=False, err=err)
|
||||
return f('')
|
||||
return f("")
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
# getpass doesn't print a newline if the user aborts input with ^C.
|
||||
# Allegedly this behavior is inherited from getpass(3).
|
||||
|
@ -109,7 +138,9 @@ def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
|||
if value_proc is None:
|
||||
value_proc = convert_type(type, default)
|
||||
|
||||
prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type)
|
||||
prompt = _build_prompt(
|
||||
text, prompt_suffix, show_default, default, show_choices, type
|
||||
)
|
||||
|
||||
while 1:
|
||||
while 1:
|
||||
|
@ -125,21 +156,22 @@ def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
|||
try:
|
||||
result = value_proc(value)
|
||||
except UsageError as e:
|
||||
echo('Error: %s' % e.message, err=err)
|
||||
echo("Error: {}".format(e.message), err=err) # noqa: B306
|
||||
continue
|
||||
if not confirmation_prompt:
|
||||
return result
|
||||
while 1:
|
||||
value2 = prompt_func('Repeat for confirmation: ')
|
||||
value2 = prompt_func("Repeat for confirmation: ")
|
||||
if value2:
|
||||
break
|
||||
if value == value2:
|
||||
return result
|
||||
echo('Error: the two entered values do not match', err=err)
|
||||
echo("Error: the two entered values do not match", err=err)
|
||||
|
||||
|
||||
def confirm(text, default=False, abort=False, prompt_suffix=': ',
|
||||
show_default=True, err=False):
|
||||
def confirm(
|
||||
text, default=False, abort=False, prompt_suffix=": ", show_default=True, err=False
|
||||
):
|
||||
"""Prompts for confirmation (yes/no question).
|
||||
|
||||
If the user aborts the input by sending a interrupt signal this
|
||||
|
@ -157,24 +189,25 @@ def confirm(text, default=False, abort=False, prompt_suffix=': ',
|
|||
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||
``stdout``, the same as with echo.
|
||||
"""
|
||||
prompt = _build_prompt(text, prompt_suffix, show_default,
|
||||
default and 'Y/n' or 'y/N')
|
||||
prompt = _build_prompt(
|
||||
text, prompt_suffix, show_default, "Y/n" if default else "y/N"
|
||||
)
|
||||
while 1:
|
||||
try:
|
||||
# Write the prompt separately so that we get nice
|
||||
# coloring through colorama on Windows
|
||||
echo(prompt, nl=False, err=err)
|
||||
value = visible_prompt_func('').lower().strip()
|
||||
value = visible_prompt_func("").lower().strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
raise Abort()
|
||||
if value in ('y', 'yes'):
|
||||
if value in ("y", "yes"):
|
||||
rv = True
|
||||
elif value in ('n', 'no'):
|
||||
elif value in ("n", "no"):
|
||||
rv = False
|
||||
elif value == '':
|
||||
elif value == "":
|
||||
rv = default
|
||||
else:
|
||||
echo('Error: invalid input', err=err)
|
||||
echo("Error: invalid input", err=err)
|
||||
continue
|
||||
break
|
||||
if abort and not rv:
|
||||
|
@ -189,7 +222,8 @@ def get_terminal_size():
|
|||
# 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)
|
||||
|
||||
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
|
||||
|
@ -207,8 +241,8 @@ def get_terminal_size():
|
|||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
cr = struct.unpack(
|
||||
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
||||
|
||||
cr = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234"))
|
||||
except Exception:
|
||||
return
|
||||
return cr
|
||||
|
@ -224,8 +258,7 @@ def get_terminal_size():
|
|||
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))
|
||||
cr = (os.environ.get("LINES", 25), os.environ.get("COLUMNS", DEFAULT_COLUMNS))
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
|
@ -251,18 +284,29 @@ def echo_via_pager(text_or_generator, color=None):
|
|||
i = iter(text_or_generator)
|
||||
|
||||
# convert every element of i to a text type if necessary
|
||||
text_generator = (el if isinstance(el, string_types) else text_type(el)
|
||||
for el in i)
|
||||
text_generator = (el if isinstance(el, string_types) else text_type(el) for el in i)
|
||||
|
||||
from ._termui_impl import pager
|
||||
|
||||
return pager(itertools.chain(text_generator, "\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, color=None):
|
||||
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,
|
||||
color=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
|
||||
|
@ -272,11 +316,17 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
|||
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
|
||||
manager is entered the progress bar is already created. 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.
|
||||
|
||||
Note: The progress bar is currently designed for use cases where the
|
||||
total progress can be expected to take at least several seconds.
|
||||
Because of this, the ProgressBar class object won't display
|
||||
progress that is considered too fast, and progress where the time
|
||||
between steps is less than a second.
|
||||
|
||||
No printing must happen or the progress bar will be unintentionally
|
||||
destroyed.
|
||||
|
||||
|
@ -342,13 +392,24 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
|||
which is not the case by default.
|
||||
"""
|
||||
from ._termui_impl import ProgressBar
|
||||
|
||||
color = resolve_color_default(color)
|
||||
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, color=color)
|
||||
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,
|
||||
color=color,
|
||||
)
|
||||
|
||||
|
||||
def clear():
|
||||
|
@ -364,13 +425,22 @@ def clear():
|
|||
# clear the screen by shelling out. Otherwise we can use an escape
|
||||
# sequence.
|
||||
if WIN:
|
||||
os.system('cls')
|
||||
os.system("cls")
|
||||
else:
|
||||
sys.stdout.write('\033[2J\033[1;1H')
|
||||
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):
|
||||
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
|
||||
|
@ -425,28 +495,28 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
|||
bits = []
|
||||
if fg:
|
||||
try:
|
||||
bits.append('\033[%dm' % (_ansi_colors[fg]))
|
||||
bits.append("\033[{}m".format(_ansi_colors[fg]))
|
||||
except KeyError:
|
||||
raise TypeError('Unknown color %r' % fg)
|
||||
raise TypeError("Unknown color '{}'".format(fg))
|
||||
if bg:
|
||||
try:
|
||||
bits.append('\033[%dm' % (_ansi_colors[bg] + 10))
|
||||
bits.append("\033[{}m".format(_ansi_colors[bg] + 10))
|
||||
except KeyError:
|
||||
raise TypeError('Unknown color %r' % bg)
|
||||
raise TypeError("Unknown color '{}'".format(bg))
|
||||
if bold is not None:
|
||||
bits.append('\033[%dm' % (1 if bold else 22))
|
||||
bits.append("\033[{}m".format(1 if bold else 22))
|
||||
if dim is not None:
|
||||
bits.append('\033[%dm' % (2 if dim else 22))
|
||||
bits.append("\033[{}m".format(2 if dim else 22))
|
||||
if underline is not None:
|
||||
bits.append('\033[%dm' % (4 if underline else 24))
|
||||
bits.append("\033[{}m".format(4 if underline else 24))
|
||||
if blink is not None:
|
||||
bits.append('\033[%dm' % (5 if blink else 25))
|
||||
bits.append("\033[{}m".format(5 if blink else 25))
|
||||
if reverse is not None:
|
||||
bits.append('\033[%dm' % (7 if reverse else 27))
|
||||
bits.append("\033[{}m".format(7 if reverse else 27))
|
||||
bits.append(text)
|
||||
if reset:
|
||||
bits.append(_ansi_reset_all)
|
||||
return ''.join(bits)
|
||||
return "".join(bits)
|
||||
|
||||
|
||||
def unstyle(text):
|
||||
|
@ -478,8 +548,9 @@ def secho(message=None, file=None, nl=True, err=False, color=None, **styles):
|
|||
return echo(message, file=file, nl=nl, err=err, color=color)
|
||||
|
||||
|
||||
def edit(text=None, editor=None, env=None, require_save=True,
|
||||
extension='.txt', filename=None):
|
||||
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
|
||||
|
@ -508,8 +579,10 @@ def edit(text=None, editor=None, env=None, require_save=True,
|
|||
file as an indirection in that case.
|
||||
"""
|
||||
from ._termui_impl import Editor
|
||||
editor = Editor(editor=editor, env=env, require_save=require_save,
|
||||
extension=extension)
|
||||
|
||||
editor = Editor(
|
||||
editor=editor, env=env, require_save=require_save, extension=extension
|
||||
)
|
||||
if filename is None:
|
||||
return editor.edit(text)
|
||||
editor.edit_file(filename)
|
||||
|
@ -538,6 +611,7 @@ def launch(url, wait=False, locate=False):
|
|||
the filesystem.
|
||||
"""
|
||||
from ._termui_impl import open_url
|
||||
|
||||
return open_url(url, wait=wait, locate=locate)
|
||||
|
||||
|
||||
|
@ -574,10 +648,11 @@ def getchar(echo=False):
|
|||
|
||||
def raw_terminal():
|
||||
from ._termui_impl import raw_terminal as f
|
||||
|
||||
return f()
|
||||
|
||||
|
||||
def pause(info='Press any key to continue ...', err=False):
|
||||
def pause(info="Press any key to continue ...", err=False):
|
||||
"""This command stops execution and waits for the user to press any
|
||||
key to continue. This is similar to the Windows batch "pause"
|
||||
command. If the program is not run through a terminal, this command
|
|
@ -1,18 +1,16 @@
|
|||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import contextlib
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from ._compat import iteritems, PY2, string_types
|
||||
|
||||
|
||||
# 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]]
|
||||
from . import formatting
|
||||
from . import termui
|
||||
from . import utils
|
||||
from ._compat import iteritems
|
||||
from ._compat import PY2
|
||||
from ._compat import string_types
|
||||
|
||||
|
||||
if PY2:
|
||||
|
@ -23,7 +21,6 @@ else:
|
|||
|
||||
|
||||
class EchoingStdin(object):
|
||||
|
||||
def __init__(self, input, output):
|
||||
self._input = input
|
||||
self._output = output
|
||||
|
@ -53,16 +50,16 @@ class EchoingStdin(object):
|
|||
|
||||
def make_input_stream(input, charset):
|
||||
# Is already an input stream.
|
||||
if hasattr(input, 'read'):
|
||||
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.')
|
||||
raise TypeError("Could not find binary reader for input stream.")
|
||||
|
||||
if input is None:
|
||||
input = b''
|
||||
input = b""
|
||||
elif not isinstance(input, bytes):
|
||||
input = input.encode(charset)
|
||||
if PY2:
|
||||
|
@ -73,13 +70,14 @@ def make_input_stream(input, charset):
|
|||
class Result(object):
|
||||
"""Holds the captured result of an invoked CLI script."""
|
||||
|
||||
def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code,
|
||||
exception, exc_info=None):
|
||||
def __init__(
|
||||
self, runner, stdout_bytes, stderr_bytes, exit_code, exception, exc_info=None
|
||||
):
|
||||
#: The runner that created the result
|
||||
self.runner = runner
|
||||
#: The standard output as bytes.
|
||||
self.stdout_bytes = stdout_bytes
|
||||
#: The standard error as bytes, or False(y) if not available
|
||||
#: The standard error as bytes, or None if not available
|
||||
self.stderr_bytes = stderr_bytes
|
||||
#: The exit code as integer.
|
||||
self.exit_code = exit_code
|
||||
|
@ -96,22 +94,22 @@ class Result(object):
|
|||
@property
|
||||
def stdout(self):
|
||||
"""The standard output as unicode string."""
|
||||
return self.stdout_bytes.decode(self.runner.charset, 'replace') \
|
||||
.replace('\r\n', '\n')
|
||||
return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
|
||||
"\r\n", "\n"
|
||||
)
|
||||
|
||||
@property
|
||||
def stderr(self):
|
||||
"""The standard error as unicode string."""
|
||||
if not self.stderr_bytes:
|
||||
if self.stderr_bytes is None:
|
||||
raise ValueError("stderr not separately captured")
|
||||
return self.stderr_bytes.decode(self.runner.charset, 'replace') \
|
||||
.replace('\r\n', '\n')
|
||||
|
||||
return self.stderr_bytes.decode(self.runner.charset, "replace").replace(
|
||||
"\r\n", "\n"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s>' % (
|
||||
type(self).__name__,
|
||||
self.exception and repr(self.exception) or 'okay',
|
||||
return "<{} {}>".format(
|
||||
type(self).__name__, repr(self.exception) if self.exception else "okay"
|
||||
)
|
||||
|
||||
|
||||
|
@ -136,10 +134,9 @@ class CliRunner(object):
|
|||
independently
|
||||
"""
|
||||
|
||||
def __init__(self, charset=None, env=None, echo_stdin=False,
|
||||
mix_stderr=True):
|
||||
def __init__(self, charset=None, env=None, echo_stdin=False, mix_stderr=True):
|
||||
if charset is None:
|
||||
charset = 'utf-8'
|
||||
charset = "utf-8"
|
||||
self.charset = charset
|
||||
self.env = env or {}
|
||||
self.echo_stdin = echo_stdin
|
||||
|
@ -150,7 +147,7 @@ class CliRunner(object):
|
|||
for it. The default is the `name` attribute or ``"root"`` if not
|
||||
set.
|
||||
"""
|
||||
return cli.name or 'root'
|
||||
return cli.name or "root"
|
||||
|
||||
def make_env(self, overrides=None):
|
||||
"""Returns the environment overrides for invoking a script."""
|
||||
|
@ -182,8 +179,8 @@ class CliRunner(object):
|
|||
old_stdin = sys.stdin
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
old_forced_width = clickpkg.formatting.FORCED_WIDTH
|
||||
clickpkg.formatting.FORCED_WIDTH = 80
|
||||
old_forced_width = formatting.FORCED_WIDTH
|
||||
formatting.FORCED_WIDTH = 80
|
||||
|
||||
env = self.make_env(env)
|
||||
|
||||
|
@ -200,12 +197,10 @@ class CliRunner(object):
|
|||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||
sys.stdout = io.TextIOWrapper(
|
||||
bytes_output, encoding=self.charset)
|
||||
sys.stdout = io.TextIOWrapper(bytes_output, encoding=self.charset)
|
||||
if not self.mix_stderr:
|
||||
bytes_error = io.BytesIO()
|
||||
sys.stderr = io.TextIOWrapper(
|
||||
bytes_error, encoding=self.charset)
|
||||
sys.stderr = io.TextIOWrapper(bytes_error, encoding=self.charset)
|
||||
|
||||
if self.mix_stderr:
|
||||
sys.stderr = sys.stdout
|
||||
|
@ -213,16 +208,16 @@ class CliRunner(object):
|
|||
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.write(prompt or "")
|
||||
val = input.readline().rstrip("\r\n")
|
||||
sys.stdout.write("{}\n".format(val))
|
||||
sys.stdout.flush()
|
||||
return val
|
||||
|
||||
def hidden_input(prompt=None):
|
||||
sys.stdout.write((prompt or '') + '\n')
|
||||
sys.stdout.write("{}\n".format(prompt or ""))
|
||||
sys.stdout.flush()
|
||||
return input.readline().rstrip('\r\n')
|
||||
return input.readline().rstrip("\r\n")
|
||||
|
||||
def _getchar(echo):
|
||||
char = sys.stdin.read(1)
|
||||
|
@ -238,14 +233,14 @@ class CliRunner(object):
|
|||
return not default_color
|
||||
return not color
|
||||
|
||||
old_visible_prompt_func = clickpkg.termui.visible_prompt_func
|
||||
old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func
|
||||
old__getchar_func = clickpkg.termui._getchar
|
||||
old_should_strip_ansi = clickpkg.utils.should_strip_ansi
|
||||
clickpkg.termui.visible_prompt_func = visible_input
|
||||
clickpkg.termui.hidden_prompt_func = hidden_input
|
||||
clickpkg.termui._getchar = _getchar
|
||||
clickpkg.utils.should_strip_ansi = should_strip_ansi
|
||||
old_visible_prompt_func = termui.visible_prompt_func
|
||||
old_hidden_prompt_func = termui.hidden_prompt_func
|
||||
old__getchar_func = termui._getchar
|
||||
old_should_strip_ansi = utils.should_strip_ansi
|
||||
termui.visible_prompt_func = visible_input
|
||||
termui.hidden_prompt_func = hidden_input
|
||||
termui._getchar = _getchar
|
||||
utils.should_strip_ansi = should_strip_ansi
|
||||
|
||||
old_env = {}
|
||||
try:
|
||||
|
@ -271,14 +266,22 @@ class CliRunner(object):
|
|||
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
|
||||
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
|
||||
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
||||
termui.visible_prompt_func = old_visible_prompt_func
|
||||
termui.hidden_prompt_func = old_hidden_prompt_func
|
||||
termui._getchar = old__getchar_func
|
||||
utils.should_strip_ansi = old_should_strip_ansi
|
||||
formatting.FORCED_WIDTH = old_forced_width
|
||||
|
||||
def invoke(self, cli, args=None, input=None, env=None,
|
||||
catch_exceptions=True, color=False, mix_stderr=False, **extra):
|
||||
def invoke(
|
||||
self,
|
||||
cli,
|
||||
args=None,
|
||||
input=None,
|
||||
env=None,
|
||||
catch_exceptions=True,
|
||||
color=False,
|
||||
**extra
|
||||
):
|
||||
"""Invokes a command in an isolated environment. The arguments are
|
||||
forwarded directly to the command line script, the `extra` keyword
|
||||
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
||||
|
@ -335,7 +338,7 @@ class CliRunner(object):
|
|||
|
||||
if not isinstance(exit_code, int):
|
||||
sys.stdout.write(str(exit_code))
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.write("\n")
|
||||
exit_code = 1
|
||||
|
||||
except Exception as e:
|
||||
|
@ -347,14 +350,19 @@ class CliRunner(object):
|
|||
finally:
|
||||
sys.stdout.flush()
|
||||
stdout = outstreams[0].getvalue()
|
||||
stderr = outstreams[1] and outstreams[1].getvalue()
|
||||
if self.mix_stderr:
|
||||
stderr = None
|
||||
else:
|
||||
stderr = outstreams[1].getvalue()
|
||||
|
||||
return Result(runner=self,
|
||||
stdout_bytes=stdout,
|
||||
stderr_bytes=stderr,
|
||||
exit_code=exit_code,
|
||||
exception=exception,
|
||||
exc_info=exc_info)
|
||||
return Result(
|
||||
runner=self,
|
||||
stdout_bytes=stdout,
|
||||
stderr_bytes=stderr,
|
||||
exit_code=exit_code,
|
||||
exception=exception,
|
||||
exc_info=exc_info,
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolated_filesystem(self):
|
||||
|
@ -370,5 +378,5 @@ class CliRunner(object):
|
|||
os.chdir(cwd)
|
||||
try:
|
||||
shutil.rmtree(t)
|
||||
except (OSError, IOError):
|
||||
except (OSError, IOError): # noqa: B014
|
||||
pass
|
|
@ -2,10 +2,16 @@ import os
|
|||
import stat
|
||||
from datetime import datetime
|
||||
|
||||
from ._compat import open_stream, text_type, filename_to_ui, \
|
||||
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
||||
from ._compat import _get_argv_encoding
|
||||
from ._compat import filename_to_ui
|
||||
from ._compat import get_filesystem_encoding
|
||||
from ._compat import get_streerror
|
||||
from ._compat import open_stream
|
||||
from ._compat import PY2
|
||||
from ._compat import text_type
|
||||
from .exceptions import BadParameter
|
||||
from .utils import safecall, LazyFile
|
||||
from .utils import LazyFile
|
||||
from .utils import safecall
|
||||
|
||||
|
||||
class ParamType(object):
|
||||
|
@ -21,6 +27,7 @@ class ParamType(object):
|
|||
This can be the case when the object is used with prompt
|
||||
inputs.
|
||||
"""
|
||||
|
||||
is_composite = False
|
||||
|
||||
#: the descriptive name of this type
|
||||
|
@ -62,7 +69,7 @@ class ParamType(object):
|
|||
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)
|
||||
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."""
|
||||
|
@ -78,7 +85,6 @@ class CompositeParamType(ParamType):
|
|||
|
||||
|
||||
class FuncParamType(ParamType):
|
||||
|
||||
def __init__(self, func):
|
||||
self.name = func.__name__
|
||||
self.func = func
|
||||
|
@ -90,22 +96,22 @@ class FuncParamType(ParamType):
|
|||
try:
|
||||
value = text_type(value)
|
||||
except UnicodeError:
|
||||
value = str(value).decode('utf-8', 'replace')
|
||||
value = str(value).decode("utf-8", "replace")
|
||||
self.fail(value, param, ctx)
|
||||
|
||||
|
||||
class UnprocessedParamType(ParamType):
|
||||
name = 'text'
|
||||
name = "text"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
return 'UNPROCESSED'
|
||||
return "UNPROCESSED"
|
||||
|
||||
|
||||
class StringParamType(ParamType):
|
||||
name = 'text'
|
||||
name = "text"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bytes):
|
||||
|
@ -118,12 +124,14 @@ class StringParamType(ParamType):
|
|||
try:
|
||||
value = value.decode(fs_enc)
|
||||
except UnicodeError:
|
||||
value = value.decode('utf-8', 'replace')
|
||||
value = value.decode("utf-8", "replace")
|
||||
else:
|
||||
value = value.decode("utf-8", "replace")
|
||||
return value
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
return 'STRING'
|
||||
return "STRING"
|
||||
|
||||
|
||||
class Choice(ParamType):
|
||||
|
@ -133,54 +141,68 @@ class Choice(ParamType):
|
|||
You should only pass a list or tuple of choices. Other iterables
|
||||
(like generators) may lead to surprising results.
|
||||
|
||||
The resulting value will always be one of the originally passed choices
|
||||
regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
|
||||
being specified.
|
||||
|
||||
See :ref:`choice-opts` for an example.
|
||||
|
||||
:param case_sensitive: Set to false to make choices case
|
||||
insensitive. Defaults to true.
|
||||
"""
|
||||
|
||||
name = 'choice'
|
||||
name = "choice"
|
||||
|
||||
def __init__(self, choices, case_sensitive=True):
|
||||
self.choices = choices
|
||||
self.case_sensitive = case_sensitive
|
||||
|
||||
def get_metavar(self, param):
|
||||
return '[%s]' % '|'.join(self.choices)
|
||||
return "[{}]".format("|".join(self.choices))
|
||||
|
||||
def get_missing_message(self, param):
|
||||
return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices)
|
||||
return "Choose from:\n\t{}.".format(",\n\t".join(self.choices))
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
# Exact match
|
||||
if value in self.choices:
|
||||
return value
|
||||
|
||||
# Match through normalization and case sensitivity
|
||||
# first do token_normalize_func, then lowercase
|
||||
# preserve original `value` to produce an accurate message in
|
||||
# `self.fail`
|
||||
normed_value = value
|
||||
normed_choices = self.choices
|
||||
normed_choices = {choice: choice for choice in self.choices}
|
||||
|
||||
if ctx is not None and \
|
||||
ctx.token_normalize_func is not None:
|
||||
if ctx is not None and ctx.token_normalize_func is not None:
|
||||
normed_value = ctx.token_normalize_func(value)
|
||||
normed_choices = [ctx.token_normalize_func(choice) for choice in
|
||||
self.choices]
|
||||
normed_choices = {
|
||||
ctx.token_normalize_func(normed_choice): original
|
||||
for normed_choice, original in normed_choices.items()
|
||||
}
|
||||
|
||||
if not self.case_sensitive:
|
||||
normed_value = normed_value.lower()
|
||||
normed_choices = [choice.lower() for choice in normed_choices]
|
||||
if PY2:
|
||||
lower = str.lower
|
||||
else:
|
||||
lower = str.casefold
|
||||
|
||||
normed_value = lower(normed_value)
|
||||
normed_choices = {
|
||||
lower(normed_choice): original
|
||||
for normed_choice, original in normed_choices.items()
|
||||
}
|
||||
|
||||
if normed_value in normed_choices:
|
||||
return normed_value
|
||||
return normed_choices[normed_value]
|
||||
|
||||
self.fail('invalid choice: %s. (choose from %s)' %
|
||||
(value, ', '.join(self.choices)), param, ctx)
|
||||
self.fail(
|
||||
"invalid choice: {}. (choose from {})".format(
|
||||
value, ", ".join(self.choices)
|
||||
),
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Choice(%r)' % list(self.choices)
|
||||
return "Choice('{}')".format(list(self.choices))
|
||||
|
||||
|
||||
class DateTime(ParamType):
|
||||
|
@ -203,17 +225,14 @@ class DateTime(ParamType):
|
|||
``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
|
||||
``'%Y-%m-%d %H:%M:%S'``.
|
||||
"""
|
||||
name = 'datetime'
|
||||
|
||||
name = "datetime"
|
||||
|
||||
def __init__(self, formats=None):
|
||||
self.formats = formats or [
|
||||
'%Y-%m-%d',
|
||||
'%Y-%m-%dT%H:%M:%S',
|
||||
'%Y-%m-%d %H:%M:%S'
|
||||
]
|
||||
self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
|
||||
|
||||
def get_metavar(self, param):
|
||||
return '[{}]'.format('|'.join(self.formats))
|
||||
return "[{}]".format("|".join(self.formats))
|
||||
|
||||
def _try_to_convert_date(self, value, format):
|
||||
try:
|
||||
|
@ -229,24 +248,26 @@ class DateTime(ParamType):
|
|||
return dtime
|
||||
|
||||
self.fail(
|
||||
'invalid datetime format: {}. (choose from {})'.format(
|
||||
value, ', '.join(self.formats)))
|
||||
"invalid datetime format: {}. (choose from {})".format(
|
||||
value, ", ".join(self.formats)
|
||||
)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return 'DateTime'
|
||||
return "DateTime"
|
||||
|
||||
|
||||
class IntParamType(ParamType):
|
||||
name = 'integer'
|
||||
name = "integer"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, UnicodeError):
|
||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||
except ValueError:
|
||||
self.fail("{} is not a valid integer".format(value), param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'INT'
|
||||
return "INT"
|
||||
|
||||
|
||||
class IntRange(IntParamType):
|
||||
|
@ -257,7 +278,8 @@ class IntRange(IntParamType):
|
|||
|
||||
See :ref:`ranges` for an example.
|
||||
"""
|
||||
name = 'integer range'
|
||||
|
||||
name = "integer range"
|
||||
|
||||
def __init__(self, min=None, max=None, clamp=False):
|
||||
self.min = min
|
||||
|
@ -271,35 +293,55 @@ class IntRange(IntParamType):
|
|||
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 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)
|
||||
self.fail(
|
||||
"{} is bigger than the maximum valid value {}.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} is smaller than the minimum valid value {}.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} is not in the valid range of {} to {}.".format(
|
||||
rv, self.min, self.max
|
||||
),
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
return rv
|
||||
|
||||
def __repr__(self):
|
||||
return 'IntRange(%r, %r)' % (self.min, self.max)
|
||||
return "IntRange({}, {})".format(self.min, self.max)
|
||||
|
||||
|
||||
class FloatParamType(ParamType):
|
||||
name = 'float'
|
||||
name = "float"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return float(value)
|
||||
except (UnicodeError, ValueError):
|
||||
self.fail('%s is not a valid floating point value' %
|
||||
value, param, ctx)
|
||||
except ValueError:
|
||||
self.fail(
|
||||
"{} is not a valid floating point value".format(value), param, ctx
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return 'FLOAT'
|
||||
return "FLOAT"
|
||||
|
||||
|
||||
class FloatRange(FloatParamType):
|
||||
|
@ -310,7 +352,8 @@ class FloatRange(FloatParamType):
|
|||
|
||||
See :ref:`ranges` for an example.
|
||||
"""
|
||||
name = 'float range'
|
||||
|
||||
name = "float range"
|
||||
|
||||
def __init__(self, min=None, max=None, clamp=False):
|
||||
self.min = min
|
||||
|
@ -324,54 +367,74 @@ class FloatRange(FloatParamType):
|
|||
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 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)
|
||||
self.fail(
|
||||
"{} is bigger than the maximum valid value {}.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} is smaller than the minimum valid value {}.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} is not in the valid range of {} to {}.".format(
|
||||
rv, self.min, self.max
|
||||
),
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
return rv
|
||||
|
||||
def __repr__(self):
|
||||
return 'FloatRange(%r, %r)' % (self.min, self.max)
|
||||
return "FloatRange({}, {})".format(self.min, self.max)
|
||||
|
||||
|
||||
class BoolParamType(ParamType):
|
||||
name = 'boolean'
|
||||
name = "boolean"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bool):
|
||||
return bool(value)
|
||||
value = value.lower()
|
||||
if value in ('true', 't', '1', 'yes', 'y'):
|
||||
if value in ("true", "t", "1", "yes", "y"):
|
||||
return True
|
||||
elif value in ('false', 'f', '0', 'no', 'n'):
|
||||
elif value in ("false", "f", "0", "no", "n"):
|
||||
return False
|
||||
self.fail('%s is not a valid boolean' % value, param, ctx)
|
||||
self.fail("{} is not a valid boolean".format(value), param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'BOOL'
|
||||
return "BOOL"
|
||||
|
||||
|
||||
class UUIDParameterType(ParamType):
|
||||
name = 'uuid'
|
||||
name = "uuid"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
import uuid
|
||||
|
||||
try:
|
||||
if PY2 and isinstance(value, text_type):
|
||||
value = value.encode('ascii')
|
||||
value = value.encode("ascii")
|
||||
return uuid.UUID(value)
|
||||
except (UnicodeError, ValueError):
|
||||
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
||||
except ValueError:
|
||||
self.fail("{} is not a valid UUID value".format(value), param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'UUID'
|
||||
return "UUID"
|
||||
|
||||
|
||||
class File(ParamType):
|
||||
|
@ -400,11 +463,13 @@ class File(ParamType):
|
|||
|
||||
See :ref:`file-args` for more information.
|
||||
"""
|
||||
name = 'filename'
|
||||
|
||||
name = "filename"
|
||||
envvar_list_splitter = os.path.pathsep
|
||||
|
||||
def __init__(self, mode='r', encoding=None, errors='strict', lazy=None,
|
||||
atomic=False):
|
||||
def __init__(
|
||||
self, mode="r", encoding=None, errors="strict", lazy=None, atomic=False
|
||||
):
|
||||
self.mode = mode
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
|
@ -414,29 +479,30 @@ class File(ParamType):
|
|||
def resolve_lazy_flag(self, value):
|
||||
if self.lazy is not None:
|
||||
return self.lazy
|
||||
if value == '-':
|
||||
if value == "-":
|
||||
return False
|
||||
elif 'w' in self.mode:
|
||||
elif "w" in self.mode:
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
if hasattr(value, 'read') or hasattr(value, 'write'):
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
@ -448,11 +514,14 @@ class File(ParamType):
|
|||
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)
|
||||
except (IOError, OSError) as e: # noqa: B014
|
||||
self.fail(
|
||||
"Could not open file: {}: {}".format(
|
||||
filename_to_ui(value), get_streerror(e)
|
||||
),
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
|
||||
|
||||
class Path(ParamType):
|
||||
|
@ -485,11 +554,20 @@ class Path(ParamType):
|
|||
unicode depending on what makes most sense given the
|
||||
input data Click deals with.
|
||||
"""
|
||||
|
||||
envvar_list_splitter = os.path.pathsep
|
||||
|
||||
def __init__(self, exists=False, file_okay=True, dir_okay=True,
|
||||
writable=False, readable=True, resolve_path=False,
|
||||
allow_dash=False, path_type=None):
|
||||
def __init__(
|
||||
self,
|
||||
exists=False,
|
||||
file_okay=True,
|
||||
dir_okay=True,
|
||||
writable=False,
|
||||
readable=True,
|
||||
resolve_path=False,
|
||||
allow_dash=False,
|
||||
path_type=None,
|
||||
):
|
||||
self.exists = exists
|
||||
self.file_okay = file_okay
|
||||
self.dir_okay = dir_okay
|
||||
|
@ -500,14 +578,14 @@ class Path(ParamType):
|
|||
self.type = path_type
|
||||
|
||||
if self.file_okay and not self.dir_okay:
|
||||
self.name = 'file'
|
||||
self.path_type = 'File'
|
||||
self.name = "file"
|
||||
self.path_type = "File"
|
||||
elif self.dir_okay and not self.file_okay:
|
||||
self.name = 'directory'
|
||||
self.path_type = 'Directory'
|
||||
self.name = "directory"
|
||||
self.path_type = "Directory"
|
||||
else:
|
||||
self.name = 'path'
|
||||
self.path_type = 'Path'
|
||||
self.name = "path"
|
||||
self.path_type = "Path"
|
||||
|
||||
def coerce_path_result(self, rv):
|
||||
if self.type is not None and not isinstance(rv, self.type):
|
||||
|
@ -520,7 +598,7 @@ class Path(ParamType):
|
|||
def convert(self, value, param, ctx):
|
||||
rv = value
|
||||
|
||||
is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-')
|
||||
is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
|
||||
|
||||
if not is_dash:
|
||||
if self.resolve_path:
|
||||
|
@ -531,31 +609,44 @@ class Path(ParamType):
|
|||
except OSError:
|
||||
if not self.exists:
|
||||
return self.coerce_path_result(rv)
|
||||
self.fail('%s "%s" does not exist.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
self.fail(
|
||||
"{} '{}' does not exist.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} '{}' is a file.".format(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)
|
||||
self.fail(
|
||||
"{} '{}' is a directory.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} '{}' is not writable.".format(
|
||||
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)
|
||||
self.fail(
|
||||
"{} '{}' is not readable.".format(
|
||||
self.path_type, filename_to_ui(value)
|
||||
),
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
|
||||
return self.coerce_path_result(rv)
|
||||
|
||||
|
@ -579,7 +670,7 @@ class Tuple(CompositeParamType):
|
|||
|
||||
@property
|
||||
def name(self):
|
||||
return "<" + " ".join(ty.name for ty in self.types) + ">"
|
||||
return "<{}>".format(" ".join(ty.name for ty in self.types))
|
||||
|
||||
@property
|
||||
def arity(self):
|
||||
|
@ -587,14 +678,16 @@ class Tuple(CompositeParamType):
|
|||
|
||||
def convert(self, value, param, ctx):
|
||||
if len(value) != len(self.types):
|
||||
raise TypeError('It would appear that nargs is set to conflict '
|
||||
'with the composite type arity.')
|
||||
raise TypeError(
|
||||
"It would appear that nargs is set to conflict with the"
|
||||
" composite type arity."
|
||||
)
|
||||
return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
|
||||
|
||||
|
||||
def convert_type(ty, default=None):
|
||||
"""Converts a callable or python ty into the most appropriate param
|
||||
ty.
|
||||
"""Converts a callable or python type into the most appropriate
|
||||
param type.
|
||||
"""
|
||||
guessed_type = False
|
||||
if ty is None and default is not None:
|
||||
|
@ -627,8 +720,9 @@ def convert_type(ty, default=None):
|
|||
if __debug__:
|
||||
try:
|
||||
if issubclass(ty, ParamType):
|
||||
raise AssertionError('Attempted to use an uninstantiated '
|
||||
'parameter type (%s).' % ty)
|
||||
raise AssertionError(
|
||||
"Attempted to use an uninstantiated parameter type ({}).".format(ty)
|
||||
)
|
||||
except TypeError:
|
||||
pass
|
||||
return FuncParamType(ty)
|
|
@ -1,34 +1,47 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from ._compat import _default_text_stderr
|
||||
from ._compat import _default_text_stdout
|
||||
from ._compat import auto_wrap_for_ansi
|
||||
from ._compat import binary_streams
|
||||
from ._compat import filename_to_ui
|
||||
from ._compat import get_filesystem_encoding
|
||||
from ._compat import get_streerror
|
||||
from ._compat import is_bytes
|
||||
from ._compat import open_stream
|
||||
from ._compat import PY2
|
||||
from ._compat import should_strip_ansi
|
||||
from ._compat import string_types
|
||||
from ._compat import strip_ansi
|
||||
from ._compat import text_streams
|
||||
from ._compat import text_type
|
||||
from ._compat import WIN
|
||||
from .globals import resolve_color_default
|
||||
|
||||
from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
||||
get_streerror, string_types, PY2, binary_streams, text_streams, \
|
||||
filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \
|
||||
_default_text_stdout, _default_text_stderr, is_bytes, WIN
|
||||
|
||||
if not PY2:
|
||||
from ._compat import _find_binary_writer
|
||||
elif WIN:
|
||||
from ._winconsole import _get_windows_argv, \
|
||||
_hash_py_argv, _initial_argv_hash
|
||||
|
||||
from ._winconsole import _get_windows_argv
|
||||
from ._winconsole import _hash_py_argv
|
||||
from ._winconsole import _initial_argv_hash
|
||||
|
||||
echo_native_types = string_types + (bytes, bytearray)
|
||||
|
||||
|
||||
def _posixify(name):
|
||||
return '-'.join(name.split()).lower()
|
||||
return "-".join(name.split()).lower()
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -38,7 +51,7 @@ def make_str(value):
|
|||
try:
|
||||
return value.decode(get_filesystem_encoding())
|
||||
except UnicodeError:
|
||||
return value.decode('utf-8', 'replace')
|
||||
return value.decode("utf-8", "replace")
|
||||
return text_type(value)
|
||||
|
||||
|
||||
|
@ -50,21 +63,21 @@ def make_default_short_help(help, max_length=45):
|
|||
done = False
|
||||
|
||||
for word in words:
|
||||
if word[-1:] == '.':
|
||||
if word[-1:] == ".":
|
||||
done = True
|
||||
new_length = result and 1 + len(word) or len(word)
|
||||
new_length = 1 + len(word) if result else len(word)
|
||||
if total_length + new_length > max_length:
|
||||
result.append('...')
|
||||
result.append("...")
|
||||
done = True
|
||||
else:
|
||||
if result:
|
||||
result.append(' ')
|
||||
result.append(" ")
|
||||
result.append(word)
|
||||
if done:
|
||||
break
|
||||
total_length += new_length
|
||||
|
||||
return ''.join(result)
|
||||
return "".join(result)
|
||||
|
||||
|
||||
class LazyFile(object):
|
||||
|
@ -74,19 +87,19 @@ class LazyFile(object):
|
|||
files for writing.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, mode='r', encoding=None, errors='strict',
|
||||
atomic=False):
|
||||
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)
|
||||
if filename == "-":
|
||||
self._f, self.should_close = open_stream(filename, mode, encoding, errors)
|
||||
else:
|
||||
if 'r' in mode:
|
||||
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.
|
||||
|
@ -100,7 +113,7 @@ class LazyFile(object):
|
|||
def __repr__(self):
|
||||
if self._f is not None:
|
||||
return repr(self._f)
|
||||
return '<unopened file %r %s>' % (self.name, self.mode)
|
||||
return "<unopened file '{}' {}>".format(self.name, self.mode)
|
||||
|
||||
def open(self):
|
||||
"""Opens the file if it's not yet open. This call might fail with
|
||||
|
@ -110,12 +123,12 @@ class LazyFile(object):
|
|||
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:
|
||||
rv, self.should_close = open_stream(
|
||||
self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
|
||||
)
|
||||
except (IOError, OSError) as e: # noqa: E402
|
||||
from .exceptions import FileError
|
||||
|
||||
raise FileError(self.name, hint=get_streerror(e))
|
||||
self._f = rv
|
||||
return rv
|
||||
|
@ -144,7 +157,6 @@ class LazyFile(object):
|
|||
|
||||
|
||||
class KeepOpenFile(object):
|
||||
|
||||
def __init__(self, file):
|
||||
self._file = file
|
||||
|
||||
|
@ -222,11 +234,11 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
message = text_type(message)
|
||||
|
||||
if nl:
|
||||
message = message or u''
|
||||
message = message or u""
|
||||
if isinstance(message, text_type):
|
||||
message += u'\n'
|
||||
message += u"\n"
|
||||
else:
|
||||
message += b'\n'
|
||||
message += b"\n"
|
||||
|
||||
# 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
|
||||
|
@ -273,11 +285,11 @@ def get_binary_stream(name):
|
|||
"""
|
||||
opener = binary_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError('Unknown standard stream %r' % name)
|
||||
raise TypeError("Unknown standard stream '{}'".format(name))
|
||||
return opener()
|
||||
|
||||
|
||||
def get_text_stream(name, encoding=None, errors='strict'):
|
||||
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
|
||||
|
@ -290,12 +302,13 @@ def get_text_stream(name, encoding=None, errors='strict'):
|
|||
"""
|
||||
opener = text_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError('Unknown standard stream %r' % name)
|
||||
raise TypeError("Unknown standard stream '{}'".format(name))
|
||||
return opener(encoding, errors)
|
||||
|
||||
|
||||
def open_file(filename, mode='r', encoding=None, errors='strict',
|
||||
lazy=False, atomic=False):
|
||||
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.
|
||||
|
@ -320,8 +333,7 @@ def open_file(filename, mode='r', encoding=None, errors='strict',
|
|||
"""
|
||||
if lazy:
|
||||
return LazyFile(filename, mode, encoding, errors, atomic=atomic)
|
||||
f, should_close = open_stream(filename, mode, encoding, errors,
|
||||
atomic=atomic)
|
||||
f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
|
||||
if not should_close:
|
||||
f = KeepOpenFile(f)
|
||||
return f
|
||||
|
@ -401,19 +413,21 @@ def get_app_dir(app_name, roaming=True, force_posix=False):
|
|||
application support folder.
|
||||
"""
|
||||
if WIN:
|
||||
key = roaming and 'APPDATA' or 'LOCALAPPDATA'
|
||||
key = "APPDATA" if roaming else "LOCALAPPDATA"
|
||||
folder = os.environ.get(key)
|
||||
if folder is None:
|
||||
folder = os.path.expanduser('~')
|
||||
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.path.expanduser("~/.{}".format(_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))
|
||||
os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
|
||||
_posixify(app_name),
|
||||
)
|
||||
|
||||
|
||||
class PacifyFlushWrapper(object):
|
||||
|
@ -433,6 +447,7 @@ class PacifyFlushWrapper(object):
|
|||
self.wrapped.flush()
|
||||
except IOError as e:
|
||||
import errno
|
||||
|
||||
if e.errno != errno.EPIPE:
|
||||
raise
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
from click.testing import CliRunner
|
||||
|
||||
import pytest
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def runner(request):
|
||||
return CliRunner()
|
||||
|
|
|
@ -1,51 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import click
|
||||
from click._compat import PY2
|
||||
from click._compat import text_type
|
||||
|
||||
|
||||
def test_nargs_star(runner):
|
||||
@click.command()
|
||||
@click.argument('src', nargs=-1)
|
||||
@click.argument('dst')
|
||||
@click.argument("src", nargs=-1)
|
||||
@click.argument("dst")
|
||||
def copy(src, dst):
|
||||
click.echo('src=%s' % '|'.join(src))
|
||||
click.echo('dst=%s' % dst)
|
||||
click.echo("src={}".format("|".join(src)))
|
||||
click.echo("dst={}".format(dst))
|
||||
|
||||
result = runner.invoke(copy, ['foo.txt', 'bar.txt', 'dir'])
|
||||
result = runner.invoke(copy, ["foo.txt", "bar.txt", "dir"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'src=foo.txt|bar.txt',
|
||||
'dst=dir',
|
||||
]
|
||||
assert result.output.splitlines() == ["src=foo.txt|bar.txt", "dst=dir"]
|
||||
|
||||
|
||||
def test_nargs_default(runner):
|
||||
try:
|
||||
with pytest.raises(TypeError, match="nargs=-1"):
|
||||
|
||||
@click.command()
|
||||
@click.argument('src', nargs=-1, default=42)
|
||||
@click.argument("src", nargs=-1, default=42)
|
||||
def copy(src):
|
||||
pass
|
||||
except TypeError as e:
|
||||
assert 'nargs=-1' in str(e)
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def test_nargs_tup(runner):
|
||||
@click.command()
|
||||
@click.argument('name', nargs=1)
|
||||
@click.argument('point', nargs=2, type=click.INT)
|
||||
@click.argument("name", nargs=1)
|
||||
@click.argument("point", nargs=2, type=click.INT)
|
||||
def copy(name, point):
|
||||
click.echo('name=%s' % name)
|
||||
click.echo('point=%d/%d' % point)
|
||||
click.echo("name={}".format(name))
|
||||
click.echo("point={0[0]}/{0[1]}".format(point))
|
||||
|
||||
result = runner.invoke(copy, ['peter', '1', '2'])
|
||||
result = runner.invoke(copy, ["peter", "1", "2"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'name=peter',
|
||||
'point=1/2',
|
||||
]
|
||||
assert result.output.splitlines() == ["name=peter", "point=1/2"]
|
||||
|
||||
|
||||
def test_nargs_tup_composite(runner):
|
||||
|
@ -57,37 +52,61 @@ def test_nargs_tup_composite(runner):
|
|||
]
|
||||
|
||||
for opts in variations:
|
||||
@click.command()
|
||||
@click.argument('item', **opts)
|
||||
def copy(item):
|
||||
click.echo('name=%s id=%d' % item)
|
||||
|
||||
result = runner.invoke(copy, ['peter', '1'])
|
||||
@click.command()
|
||||
@click.argument("item", **opts)
|
||||
def copy(item):
|
||||
click.echo("name={0[0]} id={0[1]:d}".format(item))
|
||||
|
||||
result = runner.invoke(copy, ["peter", "1"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'name=peter id=1',
|
||||
]
|
||||
assert result.output.splitlines() == ["name=peter id=1"]
|
||||
|
||||
|
||||
def test_nargs_err(runner):
|
||||
@click.command()
|
||||
@click.argument('x')
|
||||
@click.argument("x")
|
||||
def copy(x):
|
||||
click.echo(x)
|
||||
|
||||
result = runner.invoke(copy, ['foo'])
|
||||
result = runner.invoke(copy, ["foo"])
|
||||
assert not result.exception
|
||||
assert result.output == 'foo\n'
|
||||
assert result.output == "foo\n"
|
||||
|
||||
result = runner.invoke(copy, ['foo', 'bar'])
|
||||
result = runner.invoke(copy, ["foo", "bar"])
|
||||
assert result.exit_code == 2
|
||||
assert 'Got unexpected extra argument (bar)' in result.output
|
||||
assert "Got unexpected extra argument (bar)" in result.output
|
||||
|
||||
|
||||
def test_bytes_args(runner, monkeypatch):
|
||||
@click.command()
|
||||
@click.argument("arg")
|
||||
def from_bytes(arg):
|
||||
assert isinstance(
|
||||
arg, text_type
|
||||
), "UTF-8 encoded argument should be implicitly converted to Unicode"
|
||||
|
||||
# Simulate empty locale environment variables
|
||||
if PY2:
|
||||
monkeypatch.setattr(sys.stdin, "encoding", "ANSI_X3.4-1968")
|
||||
monkeypatch.setattr(sys, "getfilesystemencoding", lambda: "ANSI_X3.4-1968")
|
||||
monkeypatch.setattr(sys, "getdefaultencoding", lambda: "ascii")
|
||||
else:
|
||||
monkeypatch.setattr(sys.stdin, "encoding", "utf-8")
|
||||
monkeypatch.setattr(sys, "getfilesystemencoding", lambda: "utf-8")
|
||||
monkeypatch.setattr(sys, "getdefaultencoding", lambda: "utf-8")
|
||||
|
||||
runner.invoke(
|
||||
from_bytes,
|
||||
[u"Something outside of ASCII range: 林".encode("UTF-8")],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
|
||||
def test_file_args(runner):
|
||||
@click.command()
|
||||
@click.argument('input', type=click.File('rb'))
|
||||
@click.argument('output', type=click.File('wb'))
|
||||
@click.argument("input", type=click.File("rb"))
|
||||
@click.argument("output", type=click.File("wb"))
|
||||
def inout(input, output):
|
||||
while True:
|
||||
chunk = input.read(1024)
|
||||
|
@ -96,203 +115,201 @@ def test_file_args(runner):
|
|||
output.write(chunk)
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(inout, ['-', 'hello.txt'], input='Hey!')
|
||||
assert result.output == ''
|
||||
result = runner.invoke(inout, ["-", "hello.txt"], input="Hey!")
|
||||
assert result.output == ""
|
||||
assert result.exit_code == 0
|
||||
with open('hello.txt', 'rb') as f:
|
||||
assert f.read() == b'Hey!'
|
||||
with open("hello.txt", "rb") as f:
|
||||
assert f.read() == b"Hey!"
|
||||
|
||||
result = runner.invoke(inout, ['hello.txt', '-'])
|
||||
assert result.output == 'Hey!'
|
||||
result = runner.invoke(inout, ["hello.txt", "-"])
|
||||
assert result.output == "Hey!"
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_path_args(runner):
|
||||
@click.command()
|
||||
@click.argument('input', type=click.Path(dir_okay=False, allow_dash=True))
|
||||
@click.argument("input", type=click.Path(dir_okay=False, allow_dash=True))
|
||||
def foo(input):
|
||||
click.echo(input)
|
||||
|
||||
result = runner.invoke(foo, ['-'])
|
||||
assert result.output == '-\n'
|
||||
result = runner.invoke(foo, ["-"])
|
||||
assert result.output == "-\n"
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_file_atomics(runner):
|
||||
@click.command()
|
||||
@click.argument('output', type=click.File('wb', atomic=True))
|
||||
@click.argument("output", type=click.File("wb", atomic=True))
|
||||
def inout(output):
|
||||
output.write(b'Foo bar baz\n')
|
||||
output.write(b"Foo bar baz\n")
|
||||
output.flush()
|
||||
with open(output.name, 'rb') as f:
|
||||
with open(output.name, "rb") as f:
|
||||
old_content = f.read()
|
||||
assert old_content == b'OLD\n'
|
||||
assert old_content == b"OLD\n"
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
with open('foo.txt', 'wb') as f:
|
||||
f.write(b'OLD\n')
|
||||
result = runner.invoke(inout, ['foo.txt'], input='Hey!',
|
||||
catch_exceptions=False)
|
||||
assert result.output == ''
|
||||
with open("foo.txt", "wb") as f:
|
||||
f.write(b"OLD\n")
|
||||
result = runner.invoke(inout, ["foo.txt"], input="Hey!", catch_exceptions=False)
|
||||
assert result.output == ""
|
||||
assert result.exit_code == 0
|
||||
with open('foo.txt', 'rb') as f:
|
||||
assert f.read() == b'Foo bar baz\n'
|
||||
with open("foo.txt", "rb") as f:
|
||||
assert f.read() == b"Foo bar baz\n"
|
||||
|
||||
|
||||
def test_stdout_default(runner):
|
||||
@click.command()
|
||||
@click.argument('output', type=click.File('w'), default='-')
|
||||
@click.argument("output", type=click.File("w"), default="-")
|
||||
def inout(output):
|
||||
output.write('Foo bar baz\n')
|
||||
output.write("Foo bar baz\n")
|
||||
output.flush()
|
||||
|
||||
result = runner.invoke(inout, [])
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo bar baz\n'
|
||||
assert result.output == "Foo bar baz\n"
|
||||
|
||||
|
||||
def test_nargs_envvar(runner):
|
||||
@click.command()
|
||||
@click.option('--arg', nargs=2)
|
||||
@click.option("--arg", nargs=2)
|
||||
def cmd(arg):
|
||||
click.echo('|'.join(arg))
|
||||
click.echo("|".join(arg))
|
||||
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix='TEST',
|
||||
env={'TEST_ARG': 'foo bar'})
|
||||
result = runner.invoke(
|
||||
cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar"}
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output == 'foo|bar\n'
|
||||
assert result.output == "foo|bar\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--arg', envvar='X', nargs=2)
|
||||
@click.option("--arg", envvar="X", nargs=2)
|
||||
def cmd(arg):
|
||||
click.echo('|'.join(arg))
|
||||
click.echo("|".join(arg))
|
||||
|
||||
result = runner.invoke(cmd, [], env={'X': 'foo bar'})
|
||||
result = runner.invoke(cmd, [], env={"X": "foo bar"})
|
||||
assert not result.exception
|
||||
assert result.output == 'foo|bar\n'
|
||||
assert result.output == "foo|bar\n"
|
||||
|
||||
|
||||
def test_empty_nargs(runner):
|
||||
@click.command()
|
||||
@click.argument('arg', nargs=-1)
|
||||
@click.argument("arg", nargs=-1)
|
||||
def cmd(arg):
|
||||
click.echo('arg:' + '|'.join(arg))
|
||||
click.echo("arg:{}".format("|".join(arg)))
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'arg:\n'
|
||||
assert result.output == "arg:\n"
|
||||
|
||||
@click.command()
|
||||
@click.argument('arg', nargs=-1, required=True)
|
||||
@click.argument("arg", nargs=-1, required=True)
|
||||
def cmd2(arg):
|
||||
click.echo('arg:' + '|'.join(arg))
|
||||
click.echo("arg:{}".format("|".join(arg)))
|
||||
|
||||
result = runner.invoke(cmd2, [])
|
||||
assert result.exit_code == 2
|
||||
assert 'Missing argument "ARG..."' in result.output
|
||||
assert "Missing argument 'ARG...'" in result.output
|
||||
|
||||
|
||||
def test_missing_arg(runner):
|
||||
@click.command()
|
||||
@click.argument('arg')
|
||||
@click.argument("arg")
|
||||
def cmd(arg):
|
||||
click.echo('arg:' + arg)
|
||||
click.echo("arg:{}".format(arg))
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.exit_code == 2
|
||||
assert 'Missing argument "ARG".' in result.output
|
||||
assert "Missing argument 'ARG'." in result.output
|
||||
|
||||
|
||||
def test_missing_argument_string_cast():
|
||||
ctx = click.Context(click.Command(""))
|
||||
|
||||
with pytest.raises(click.MissingParameter) as excinfo:
|
||||
click.Argument(["a"], required=True).full_process_value(ctx, None)
|
||||
|
||||
assert str(excinfo.value) == "missing parameter: a"
|
||||
|
||||
|
||||
def test_implicit_non_required(runner):
|
||||
@click.command()
|
||||
@click.argument('f', default='test')
|
||||
@click.argument("f", default="test")
|
||||
def cli(f):
|
||||
click.echo(f)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'test\n'
|
||||
assert result.output == "test\n"
|
||||
|
||||
|
||||
def test_eat_options(runner):
|
||||
@click.command()
|
||||
@click.option('-f')
|
||||
@click.argument('files', nargs=-1)
|
||||
@click.option("-f")
|
||||
@click.argument("files", nargs=-1)
|
||||
def cmd(f, files):
|
||||
for filename in files:
|
||||
click.echo(filename)
|
||||
click.echo(f)
|
||||
|
||||
result = runner.invoke(cmd, ['--', '-foo', 'bar'])
|
||||
assert result.output.splitlines() == [
|
||||
'-foo',
|
||||
'bar',
|
||||
'',
|
||||
]
|
||||
result = runner.invoke(cmd, ["--", "-foo", "bar"])
|
||||
assert result.output.splitlines() == ["-foo", "bar", ""]
|
||||
|
||||
result = runner.invoke(cmd, ['-f', '-x', '--', '-foo', 'bar'])
|
||||
assert result.output.splitlines() == [
|
||||
'-foo',
|
||||
'bar',
|
||||
'-x',
|
||||
]
|
||||
result = runner.invoke(cmd, ["-f", "-x", "--", "-foo", "bar"])
|
||||
assert result.output.splitlines() == ["-foo", "bar", "-x"]
|
||||
|
||||
|
||||
def test_nargs_star_ordering(runner):
|
||||
@click.command()
|
||||
@click.argument('a', nargs=-1)
|
||||
@click.argument('b')
|
||||
@click.argument('c')
|
||||
@click.argument("a", nargs=-1)
|
||||
@click.argument("b")
|
||||
@click.argument("c")
|
||||
def cmd(a, b, c):
|
||||
for arg in (a, b, c):
|
||||
click.echo(arg)
|
||||
|
||||
result = runner.invoke(cmd, ['a', 'b', 'c'])
|
||||
assert result.output.splitlines() == [
|
||||
PY2 and "(u'a',)" or "('a',)",
|
||||
'b',
|
||||
'c',
|
||||
]
|
||||
result = runner.invoke(cmd, ["a", "b", "c"])
|
||||
assert result.output.splitlines() == ["(u'a',)" if PY2 else "('a',)", "b", "c"]
|
||||
|
||||
|
||||
def test_nargs_specified_plus_star_ordering(runner):
|
||||
@click.command()
|
||||
@click.argument('a', nargs=-1)
|
||||
@click.argument('b')
|
||||
@click.argument('c', nargs=2)
|
||||
@click.argument("a", nargs=-1)
|
||||
@click.argument("b")
|
||||
@click.argument("c", nargs=2)
|
||||
def cmd(a, b, c):
|
||||
for arg in (a, b, c):
|
||||
click.echo(arg)
|
||||
|
||||
result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f'])
|
||||
result = runner.invoke(cmd, ["a", "b", "c", "d", "e", "f"])
|
||||
assert result.output.splitlines() == [
|
||||
PY2 and "(u'a', u'b', u'c')" or "('a', 'b', 'c')",
|
||||
'd',
|
||||
PY2 and "(u'e', u'f')" or "('e', 'f')",
|
||||
"(u'a', u'b', u'c')" if PY2 else "('a', 'b', 'c')",
|
||||
"d",
|
||||
"(u'e', u'f')" if PY2 else "('e', 'f')",
|
||||
]
|
||||
|
||||
|
||||
def test_defaults_for_nargs(runner):
|
||||
@click.command()
|
||||
@click.argument('a', nargs=2, type=int, default=(1, 2))
|
||||
@click.argument("a", nargs=2, type=int, default=(1, 2))
|
||||
def cmd(a):
|
||||
x, y = a
|
||||
click.echo(x + y)
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.output.strip() == '3'
|
||||
assert result.output.strip() == "3"
|
||||
|
||||
result = runner.invoke(cmd, ['3', '4'])
|
||||
assert result.output.strip() == '7'
|
||||
result = runner.invoke(cmd, ["3", "4"])
|
||||
assert result.output.strip() == "7"
|
||||
|
||||
result = runner.invoke(cmd, ['3'])
|
||||
result = runner.invoke(cmd, ["3"])
|
||||
assert result.exception is not None
|
||||
assert 'argument a takes 2 values' in result.output
|
||||
assert "argument a takes 2 values" in result.output
|
||||
|
||||
|
||||
def test_multiple_param_decls_not_allowed(runner):
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@click.command()
|
||||
@click.argument('x', click.Choice(['a', 'b']))
|
||||
@click.argument("x", click.Choice(["a", "b"]))
|
||||
def copy(x):
|
||||
click.echo(x)
|
||||
|
|
|
@ -1,98 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
import click
|
||||
from click._bashcomplete import get_choices
|
||||
|
||||
|
||||
def choices_without_help(cli, args, incomplete):
|
||||
completions = get_choices(cli, 'dummy', args, incomplete)
|
||||
completions = get_choices(cli, "dummy", args, incomplete)
|
||||
return [c[0] for c in completions]
|
||||
|
||||
|
||||
def choices_with_help(cli, args, incomplete):
|
||||
return list(get_choices(cli, 'dummy', args, incomplete))
|
||||
return list(get_choices(cli, "dummy", args, incomplete))
|
||||
|
||||
|
||||
def test_single_command():
|
||||
@click.command()
|
||||
@click.option('--local-opt')
|
||||
@click.option("--local-opt")
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--local-opt']
|
||||
assert choices_without_help(cli, [], '') == []
|
||||
assert choices_without_help(cli, [], "-") == ["--local-opt"]
|
||||
assert choices_without_help(cli, [], "") == []
|
||||
|
||||
|
||||
def test_boolean_flag():
|
||||
@click.command()
|
||||
@click.option('--shout/--no-shout', default=False)
|
||||
@click.option("--shout/--no-shout", default=False)
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--shout', '--no-shout']
|
||||
assert choices_without_help(cli, [], "-") == ["--shout", "--no-shout"]
|
||||
|
||||
|
||||
def test_multi_value_option():
|
||||
@click.group()
|
||||
@click.option('--pos', nargs=2, type=float)
|
||||
@click.option("--pos", nargs=2, type=float)
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--local-opt')
|
||||
@click.option("--local-opt")
|
||||
def sub(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--pos']
|
||||
assert choices_without_help(cli, ['--pos'], '') == []
|
||||
assert choices_without_help(cli, ['--pos', '1.0'], '') == []
|
||||
assert choices_without_help(cli, ['--pos', '1.0', '1.0'], '') == ['sub']
|
||||
assert choices_without_help(cli, [], "-") == ["--pos"]
|
||||
assert choices_without_help(cli, ["--pos"], "") == []
|
||||
assert choices_without_help(cli, ["--pos", "1.0"], "") == []
|
||||
assert choices_without_help(cli, ["--pos", "1.0", "1.0"], "") == ["sub"]
|
||||
|
||||
|
||||
def test_multi_option():
|
||||
@click.command()
|
||||
@click.option('--message', '-m', multiple=True)
|
||||
@click.option("--message", "-m", multiple=True)
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--message', '-m']
|
||||
assert choices_without_help(cli, ['-m'], '') == []
|
||||
assert choices_without_help(cli, [], "-") == ["--message", "-m"]
|
||||
assert choices_without_help(cli, ["-m"], "") == []
|
||||
|
||||
|
||||
def test_small_chain():
|
||||
@click.group()
|
||||
@click.option('--global-opt')
|
||||
@click.option("--global-opt")
|
||||
def cli(global_opt):
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--local-opt')
|
||||
@click.option("--local-opt")
|
||||
def sub(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '') == ['sub']
|
||||
assert choices_without_help(cli, [], '-') == ['--global-opt']
|
||||
assert choices_without_help(cli, ['sub'], '') == []
|
||||
assert choices_without_help(cli, ['sub'], '-') == ['--local-opt']
|
||||
assert choices_without_help(cli, [], "") == ["sub"]
|
||||
assert choices_without_help(cli, [], "-") == ["--global-opt"]
|
||||
assert choices_without_help(cli, ["sub"], "") == []
|
||||
assert choices_without_help(cli, ["sub"], "-") == ["--local-opt"]
|
||||
|
||||
|
||||
def test_long_chain():
|
||||
@click.group('cli')
|
||||
@click.option('--cli-opt')
|
||||
@click.group("cli")
|
||||
@click.option("--cli-opt")
|
||||
def cli(cli_opt):
|
||||
pass
|
||||
|
||||
@cli.group('asub')
|
||||
@click.option('--asub-opt')
|
||||
@cli.group("asub")
|
||||
@click.option("--asub-opt")
|
||||
def asub(asub_opt):
|
||||
pass
|
||||
|
||||
@asub.group('bsub')
|
||||
@click.option('--bsub-opt')
|
||||
@asub.group("bsub")
|
||||
@click.option("--bsub-opt")
|
||||
def bsub(bsub_opt):
|
||||
pass
|
||||
|
||||
COLORS = ['red', 'green', 'blue']
|
||||
COLORS = ["red", "green", "blue"]
|
||||
|
||||
def get_colors(ctx, args, incomplete):
|
||||
for c in COLORS:
|
||||
if c.startswith(incomplete):
|
||||
|
@ -103,197 +105,237 @@ def test_long_chain():
|
|||
if incomplete in c:
|
||||
yield c
|
||||
|
||||
CSUB_OPT_CHOICES = ['foo', 'bar']
|
||||
CSUB_CHOICES = ['bar', 'baz']
|
||||
@bsub.command('csub')
|
||||
@click.option('--csub-opt', type=click.Choice(CSUB_OPT_CHOICES))
|
||||
@click.option('--csub', type=click.Choice(CSUB_CHOICES))
|
||||
@click.option('--search-color', autocompletion=search_colors)
|
||||
@click.argument('color', autocompletion=get_colors)
|
||||
CSUB_OPT_CHOICES = ["foo", "bar"]
|
||||
CSUB_CHOICES = ["bar", "baz"]
|
||||
|
||||
@bsub.command("csub")
|
||||
@click.option("--csub-opt", type=click.Choice(CSUB_OPT_CHOICES))
|
||||
@click.option("--csub", type=click.Choice(CSUB_CHOICES))
|
||||
@click.option("--search-color", autocompletion=search_colors)
|
||||
@click.argument("color", autocompletion=get_colors)
|
||||
def csub(csub_opt, color):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--cli-opt']
|
||||
assert choices_without_help(cli, [], '') == ['asub']
|
||||
assert choices_without_help(cli, ['asub'], '-') == ['--asub-opt']
|
||||
assert choices_without_help(cli, ['asub'], '') == ['bsub']
|
||||
assert choices_without_help(cli, ['asub', 'bsub'], '-') == ['--bsub-opt']
|
||||
assert choices_without_help(cli, ['asub', 'bsub'], '') == ['csub']
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '-') == ['--csub-opt', '--csub', '--search-color']
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--csub-opt'], '') == CSUB_OPT_CHOICES
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '--csub') == ['--csub-opt', '--csub']
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--csub'], '') == CSUB_CHOICES
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--csub-opt'], 'f') == ['foo']
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], '') == COLORS
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub'], 'b') == ['blue']
|
||||
assert choices_without_help(cli, ['asub', 'bsub', 'csub', '--search-color'], 'een') == ['green']
|
||||
assert choices_without_help(cli, [], "-") == ["--cli-opt"]
|
||||
assert choices_without_help(cli, [], "") == ["asub"]
|
||||
assert choices_without_help(cli, ["asub"], "-") == ["--asub-opt"]
|
||||
assert choices_without_help(cli, ["asub"], "") == ["bsub"]
|
||||
assert choices_without_help(cli, ["asub", "bsub"], "-") == ["--bsub-opt"]
|
||||
assert choices_without_help(cli, ["asub", "bsub"], "") == ["csub"]
|
||||
assert choices_without_help(cli, ["asub", "bsub", "csub"], "-") == [
|
||||
"--csub-opt",
|
||||
"--csub",
|
||||
"--search-color",
|
||||
]
|
||||
assert (
|
||||
choices_without_help(cli, ["asub", "bsub", "csub", "--csub-opt"], "")
|
||||
== CSUB_OPT_CHOICES
|
||||
)
|
||||
assert choices_without_help(cli, ["asub", "bsub", "csub"], "--csub") == [
|
||||
"--csub-opt",
|
||||
"--csub",
|
||||
]
|
||||
assert (
|
||||
choices_without_help(cli, ["asub", "bsub", "csub", "--csub"], "")
|
||||
== CSUB_CHOICES
|
||||
)
|
||||
assert choices_without_help(cli, ["asub", "bsub", "csub", "--csub-opt"], "f") == [
|
||||
"foo"
|
||||
]
|
||||
assert choices_without_help(cli, ["asub", "bsub", "csub"], "") == COLORS
|
||||
assert choices_without_help(cli, ["asub", "bsub", "csub"], "b") == ["blue"]
|
||||
assert choices_without_help(
|
||||
cli, ["asub", "bsub", "csub", "--search-color"], "een"
|
||||
) == ["green"]
|
||||
|
||||
|
||||
def test_chaining():
|
||||
@click.group('cli', chain=True)
|
||||
@click.option('--cli-opt')
|
||||
@click.argument('arg', type=click.Choice(['cliarg1', 'cliarg2']))
|
||||
@click.group("cli", chain=True)
|
||||
@click.option("--cli-opt")
|
||||
@click.argument("arg", type=click.Choice(["cliarg1", "cliarg2"]))
|
||||
def cli(cli_opt, arg):
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--asub-opt')
|
||||
@click.option("--asub-opt")
|
||||
def asub(asub_opt):
|
||||
pass
|
||||
|
||||
@cli.command(help='bsub help')
|
||||
@click.option('--bsub-opt')
|
||||
@click.argument('arg', type=click.Choice(['arg1', 'arg2']))
|
||||
@cli.command(help="bsub help")
|
||||
@click.option("--bsub-opt")
|
||||
@click.argument("arg", type=click.Choice(["arg1", "arg2"]))
|
||||
def bsub(bsub_opt, arg):
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--csub-opt')
|
||||
@click.argument('arg', type=click.Choice(['carg1', 'carg2']), default='carg1')
|
||||
@click.option("--csub-opt")
|
||||
@click.argument("arg", type=click.Choice(["carg1", "carg2"]), default="carg1")
|
||||
def csub(csub_opt, arg):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--cli-opt']
|
||||
assert choices_without_help(cli, [], '') == ['cliarg1', 'cliarg2']
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub'], '-') == ['--asub-opt']
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub'], '') == ['bsub', 'csub']
|
||||
assert choices_without_help(cli, ['cliarg1', 'bsub'], '') == ['arg1', 'arg2']
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub', '--asub-opt'], '') == []
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub', '--asub-opt', '5', 'bsub'], '-') == ['--bsub-opt']
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub', 'bsub'], '-') == ['--bsub-opt']
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub', 'csub'], '') == ['carg1', 'carg2']
|
||||
assert choices_without_help(cli, ['cliarg1', 'bsub', 'arg1', 'csub'], '') == ['carg1', 'carg2']
|
||||
assert choices_without_help(cli, ['cliarg1', 'asub', 'csub'], '-') == ['--csub-opt']
|
||||
assert choices_with_help(cli, ['cliarg1', 'asub'], 'b') == [('bsub', 'bsub help')]
|
||||
assert choices_without_help(cli, [], "-") == ["--cli-opt"]
|
||||
assert choices_without_help(cli, [], "") == ["cliarg1", "cliarg2"]
|
||||
assert choices_without_help(cli, ["cliarg1", "asub"], "-") == ["--asub-opt"]
|
||||
assert choices_without_help(cli, ["cliarg1", "asub"], "") == ["bsub", "csub"]
|
||||
assert choices_without_help(cli, ["cliarg1", "bsub"], "") == ["arg1", "arg2"]
|
||||
assert choices_without_help(cli, ["cliarg1", "asub", "--asub-opt"], "") == []
|
||||
assert choices_without_help(
|
||||
cli, ["cliarg1", "asub", "--asub-opt", "5", "bsub"], "-"
|
||||
) == ["--bsub-opt"]
|
||||
assert choices_without_help(cli, ["cliarg1", "asub", "bsub"], "-") == ["--bsub-opt"]
|
||||
assert choices_without_help(cli, ["cliarg1", "asub", "csub"], "") == [
|
||||
"carg1",
|
||||
"carg2",
|
||||
]
|
||||
assert choices_without_help(cli, ["cliarg1", "bsub", "arg1", "csub"], "") == [
|
||||
"carg1",
|
||||
"carg2",
|
||||
]
|
||||
assert choices_without_help(cli, ["cliarg1", "asub", "csub"], "-") == ["--csub-opt"]
|
||||
assert choices_with_help(cli, ["cliarg1", "asub"], "b") == [("bsub", "bsub help")]
|
||||
|
||||
|
||||
def test_argument_choice():
|
||||
@click.command()
|
||||
@click.argument('arg1', required=True, type=click.Choice(['arg11', 'arg12']))
|
||||
@click.argument('arg2', type=click.Choice(['arg21', 'arg22']), default='arg21')
|
||||
@click.argument('arg3', type=click.Choice(['arg', 'argument']), default='arg')
|
||||
@click.argument("arg1", required=True, type=click.Choice(["arg11", "arg12"]))
|
||||
@click.argument("arg2", type=click.Choice(["arg21", "arg22"]), default="arg21")
|
||||
@click.argument("arg3", type=click.Choice(["arg", "argument"]), default="arg")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '') == ['arg11', 'arg12']
|
||||
assert choices_without_help(cli, [], 'arg') == ['arg11', 'arg12']
|
||||
assert choices_without_help(cli, ['arg11'], '') == ['arg21', 'arg22']
|
||||
assert choices_without_help(cli, ['arg12', 'arg21'], '') == ['arg', 'argument']
|
||||
assert choices_without_help(cli, ['arg12', 'arg21'], 'argu') == ['argument']
|
||||
assert choices_without_help(cli, [], "") == ["arg11", "arg12"]
|
||||
assert choices_without_help(cli, [], "arg") == ["arg11", "arg12"]
|
||||
assert choices_without_help(cli, ["arg11"], "") == ["arg21", "arg22"]
|
||||
assert choices_without_help(cli, ["arg12", "arg21"], "") == ["arg", "argument"]
|
||||
assert choices_without_help(cli, ["arg12", "arg21"], "argu") == ["argument"]
|
||||
|
||||
|
||||
def test_option_choice():
|
||||
@click.command()
|
||||
@click.option('--opt1', type=click.Choice(['opt11', 'opt12']), help='opt1 help')
|
||||
@click.option('--opt2', type=click.Choice(['opt21', 'opt22']), default='opt21')
|
||||
@click.option('--opt3', type=click.Choice(['opt', 'option']))
|
||||
@click.option("--opt1", type=click.Choice(["opt11", "opt12"]), help="opt1 help")
|
||||
@click.option("--opt2", type=click.Choice(["opt21", "opt22"]), default="opt21")
|
||||
@click.option("--opt3", type=click.Choice(["opt", "option"]))
|
||||
def cli():
|
||||
pass
|
||||
|
||||
assert choices_with_help(cli, [], '-') == [('--opt1', 'opt1 help'),
|
||||
('--opt2', None),
|
||||
('--opt3', None)]
|
||||
assert choices_without_help(cli, [], '--opt') == ['--opt1', '--opt2', '--opt3']
|
||||
assert choices_without_help(cli, [], '--opt1=') == ['opt11', 'opt12']
|
||||
assert choices_without_help(cli, [], '--opt2=') == ['opt21', 'opt22']
|
||||
assert choices_without_help(cli, ['--opt2'], '=') == ['opt21', 'opt22']
|
||||
assert choices_without_help(cli, ['--opt2', '='], 'opt') == ['opt21', 'opt22']
|
||||
assert choices_without_help(cli, ['--opt1'], '') == ['opt11', 'opt12']
|
||||
assert choices_without_help(cli, ['--opt2'], '') == ['opt21', 'opt22']
|
||||
assert choices_without_help(cli, ['--opt1', 'opt11', '--opt2'], '') == ['opt21', 'opt22']
|
||||
assert choices_without_help(cli, ['--opt2', 'opt21'], '-') == ['--opt1', '--opt3']
|
||||
assert choices_without_help(cli, ['--opt1', 'opt11'], '-') == ['--opt2', '--opt3']
|
||||
assert choices_without_help(cli, ['--opt1'], 'opt') == ['opt11', 'opt12']
|
||||
assert choices_without_help(cli, ['--opt3'], 'opti') == ['option']
|
||||
assert choices_with_help(cli, [], "-") == [
|
||||
("--opt1", "opt1 help"),
|
||||
("--opt2", None),
|
||||
("--opt3", None),
|
||||
]
|
||||
assert choices_without_help(cli, [], "--opt") == ["--opt1", "--opt2", "--opt3"]
|
||||
assert choices_without_help(cli, [], "--opt1=") == ["opt11", "opt12"]
|
||||
assert choices_without_help(cli, [], "--opt2=") == ["opt21", "opt22"]
|
||||
assert choices_without_help(cli, ["--opt2"], "=") == ["opt21", "opt22"]
|
||||
assert choices_without_help(cli, ["--opt2", "="], "opt") == ["opt21", "opt22"]
|
||||
assert choices_without_help(cli, ["--opt1"], "") == ["opt11", "opt12"]
|
||||
assert choices_without_help(cli, ["--opt2"], "") == ["opt21", "opt22"]
|
||||
assert choices_without_help(cli, ["--opt1", "opt11", "--opt2"], "") == [
|
||||
"opt21",
|
||||
"opt22",
|
||||
]
|
||||
assert choices_without_help(cli, ["--opt2", "opt21"], "-") == ["--opt1", "--opt3"]
|
||||
assert choices_without_help(cli, ["--opt1", "opt11"], "-") == ["--opt2", "--opt3"]
|
||||
assert choices_without_help(cli, ["--opt1"], "opt") == ["opt11", "opt12"]
|
||||
assert choices_without_help(cli, ["--opt3"], "opti") == ["option"]
|
||||
|
||||
assert choices_without_help(cli, ['--opt1', 'invalid_opt'], '-') == ['--opt2', '--opt3']
|
||||
assert choices_without_help(cli, ["--opt1", "invalid_opt"], "-") == [
|
||||
"--opt2",
|
||||
"--opt3",
|
||||
]
|
||||
|
||||
|
||||
def test_option_and_arg_choice():
|
||||
@click.command()
|
||||
@click.option('--opt1', type=click.Choice(['opt11', 'opt12']))
|
||||
@click.argument('arg1', required=False, type=click.Choice(['arg11', 'arg12']))
|
||||
@click.option('--opt2', type=click.Choice(['opt21', 'opt22']))
|
||||
@click.option("--opt1", type=click.Choice(["opt11", "opt12"]))
|
||||
@click.argument("arg1", required=False, type=click.Choice(["arg11", "arg12"]))
|
||||
@click.option("--opt2", type=click.Choice(["opt21", "opt22"]))
|
||||
def cli():
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, ['--opt1'], '') == ['opt11', 'opt12']
|
||||
assert choices_without_help(cli, [''], '--opt1=') == ['opt11', 'opt12']
|
||||
assert choices_without_help(cli, [], '') == ['arg11', 'arg12']
|
||||
assert choices_without_help(cli, ['--opt2'], '') == ['opt21', 'opt22']
|
||||
assert choices_without_help(cli, ['arg11'], '--opt') == ['--opt1', '--opt2']
|
||||
assert choices_without_help(cli, [], '--opt') == ['--opt1', '--opt2']
|
||||
assert choices_without_help(cli, ["--opt1"], "") == ["opt11", "opt12"]
|
||||
assert choices_without_help(cli, [""], "--opt1=") == ["opt11", "opt12"]
|
||||
assert choices_without_help(cli, [], "") == ["arg11", "arg12"]
|
||||
assert choices_without_help(cli, ["--opt2"], "") == ["opt21", "opt22"]
|
||||
assert choices_without_help(cli, ["arg11"], "--opt") == ["--opt1", "--opt2"]
|
||||
assert choices_without_help(cli, [], "--opt") == ["--opt1", "--opt2"]
|
||||
|
||||
|
||||
def test_boolean_flag_choice():
|
||||
@click.command()
|
||||
@click.option('--shout/--no-shout', default=False)
|
||||
@click.argument('arg', required=False, type=click.Choice(['arg1', 'arg2']))
|
||||
@click.option("--shout/--no-shout", default=False)
|
||||
@click.argument("arg", required=False, type=click.Choice(["arg1", "arg2"]))
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '-') == ['--shout', '--no-shout']
|
||||
assert choices_without_help(cli, ['--shout'], '') == ['arg1', 'arg2']
|
||||
assert choices_without_help(cli, [], "-") == ["--shout", "--no-shout"]
|
||||
assert choices_without_help(cli, ["--shout"], "") == ["arg1", "arg2"]
|
||||
|
||||
|
||||
def test_multi_value_option_choice():
|
||||
@click.command()
|
||||
@click.option('--pos', nargs=2, type=click.Choice(['pos1', 'pos2']))
|
||||
@click.argument('arg', required=False, type=click.Choice(['arg1', 'arg2']))
|
||||
@click.option("--pos", nargs=2, type=click.Choice(["pos1", "pos2"]))
|
||||
@click.argument("arg", required=False, type=click.Choice(["arg1", "arg2"]))
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, ['--pos'], '') == ['pos1', 'pos2']
|
||||
assert choices_without_help(cli, ['--pos', 'pos1'], '') == ['pos1', 'pos2']
|
||||
assert choices_without_help(cli, ['--pos', 'pos1', 'pos2'], '') == ['arg1', 'arg2']
|
||||
assert choices_without_help(cli, ['--pos', 'pos1', 'pos2', 'arg1'], '') == []
|
||||
assert choices_without_help(cli, ["--pos"], "") == ["pos1", "pos2"]
|
||||
assert choices_without_help(cli, ["--pos", "pos1"], "") == ["pos1", "pos2"]
|
||||
assert choices_without_help(cli, ["--pos", "pos1", "pos2"], "") == ["arg1", "arg2"]
|
||||
assert choices_without_help(cli, ["--pos", "pos1", "pos2", "arg1"], "") == []
|
||||
|
||||
|
||||
def test_multi_option_choice():
|
||||
@click.command()
|
||||
@click.option('--message', '-m', multiple=True, type=click.Choice(['m1', 'm2']))
|
||||
@click.argument('arg', required=False, type=click.Choice(['arg1', 'arg2']))
|
||||
@click.option("--message", "-m", multiple=True, type=click.Choice(["m1", "m2"]))
|
||||
@click.argument("arg", required=False, type=click.Choice(["arg1", "arg2"]))
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, ['-m'], '') == ['m1', 'm2']
|
||||
assert choices_without_help(cli, ['-m', 'm1', '-m'], '') == ['m1', 'm2']
|
||||
assert choices_without_help(cli, ['-m', 'm1'], '') == ['arg1', 'arg2']
|
||||
assert choices_without_help(cli, ["-m"], "") == ["m1", "m2"]
|
||||
assert choices_without_help(cli, ["-m", "m1", "-m"], "") == ["m1", "m2"]
|
||||
assert choices_without_help(cli, ["-m", "m1"], "") == ["arg1", "arg2"]
|
||||
|
||||
|
||||
def test_variadic_argument_choice():
|
||||
@click.command()
|
||||
@click.option('--opt', type=click.Choice(['opt1', 'opt2']))
|
||||
@click.argument('src', nargs=-1, type=click.Choice(['src1', 'src2']))
|
||||
@click.option("--opt", type=click.Choice(["opt1", "opt2"]))
|
||||
@click.argument("src", nargs=-1, type=click.Choice(["src1", "src2"]))
|
||||
def cli(local_opt):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, ['src1', 'src2'], '') == ['src1', 'src2']
|
||||
assert choices_without_help(cli, ['src1', 'src2'], '--o') == ['--opt']
|
||||
assert choices_without_help(cli, ['src1', 'src2', '--opt'], '') == ['opt1', 'opt2']
|
||||
assert choices_without_help(cli, ['src1', 'src2'], '') == ['src1', 'src2']
|
||||
assert choices_without_help(cli, ["src1", "src2"], "") == ["src1", "src2"]
|
||||
assert choices_without_help(cli, ["src1", "src2"], "--o") == ["--opt"]
|
||||
assert choices_without_help(cli, ["src1", "src2", "--opt"], "") == ["opt1", "opt2"]
|
||||
assert choices_without_help(cli, ["src1", "src2"], "") == ["src1", "src2"]
|
||||
|
||||
|
||||
def test_variadic_argument_complete():
|
||||
|
||||
def _complete(ctx, args, incomplete):
|
||||
return ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
|
||||
return ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]
|
||||
|
||||
@click.group()
|
||||
def entrypoint():
|
||||
pass
|
||||
|
||||
@click.command()
|
||||
@click.option('--opt', autocompletion=_complete)
|
||||
@click.argument('arg', nargs=-1)
|
||||
@click.option("--opt", autocompletion=_complete)
|
||||
@click.argument("arg", nargs=-1)
|
||||
def subcommand(opt, arg):
|
||||
pass
|
||||
|
||||
entrypoint.add_command(subcommand)
|
||||
|
||||
assert choices_without_help(entrypoint, ['subcommand', '--opt'], '') == _complete(0,0,0)
|
||||
assert choices_without_help(entrypoint, ['subcommand', 'whatever', '--opt'], '') == _complete(0,0,0)
|
||||
assert choices_without_help(entrypoint, ['subcommand', 'whatever', '--opt', 'abc'], '') == []
|
||||
assert choices_without_help(entrypoint, ["subcommand", "--opt"], "") == _complete(
|
||||
0, 0, 0
|
||||
)
|
||||
assert choices_without_help(
|
||||
entrypoint, ["subcommand", "whatever", "--opt"], ""
|
||||
) == _complete(0, 0, 0)
|
||||
assert (
|
||||
choices_without_help(entrypoint, ["subcommand", "whatever", "--opt", "abc"], "")
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_long_chain_choice():
|
||||
|
@ -302,19 +344,25 @@ def test_long_chain_choice():
|
|||
pass
|
||||
|
||||
@cli.group()
|
||||
@click.option('--sub-opt', type=click.Choice(['subopt1', 'subopt2']))
|
||||
@click.argument('sub-arg', required=False, type=click.Choice(['subarg1', 'subarg2']))
|
||||
@click.option("--sub-opt", type=click.Choice(["subopt1", "subopt2"]))
|
||||
@click.argument(
|
||||
"sub-arg", required=False, type=click.Choice(["subarg1", "subarg2"])
|
||||
)
|
||||
def sub(sub_opt, sub_arg):
|
||||
pass
|
||||
|
||||
@sub.command(short_help='bsub help')
|
||||
@click.option('--bsub-opt', type=click.Choice(['bsubopt1', 'bsubopt2']))
|
||||
@click.argument('bsub-arg1', required=False, type=click.Choice(['bsubarg1', 'bsubarg2']))
|
||||
@click.argument('bbsub-arg2', required=False, type=click.Choice(['bbsubarg1', 'bbsubarg2']))
|
||||
@sub.command(short_help="bsub help")
|
||||
@click.option("--bsub-opt", type=click.Choice(["bsubopt1", "bsubopt2"]))
|
||||
@click.argument(
|
||||
"bsub-arg1", required=False, type=click.Choice(["bsubarg1", "bsubarg2"])
|
||||
)
|
||||
@click.argument(
|
||||
"bbsub-arg2", required=False, type=click.Choice(["bbsubarg1", "bbsubarg2"])
|
||||
)
|
||||
def bsub(bsub_opt):
|
||||
pass
|
||||
|
||||
@sub.group('csub')
|
||||
@sub.group("csub")
|
||||
def csub():
|
||||
pass
|
||||
|
||||
|
@ -322,24 +370,42 @@ def test_long_chain_choice():
|
|||
def dsub():
|
||||
pass
|
||||
|
||||
assert choices_with_help(cli, ['sub', 'subarg1'], '') == [('bsub', 'bsub help'), ('csub', '')]
|
||||
assert choices_without_help(cli, ['sub'], '') == ['subarg1', 'subarg2']
|
||||
assert choices_without_help(cli, ['sub', '--sub-opt'], '') == ['subopt1', 'subopt2']
|
||||
assert choices_without_help(cli, ['sub', '--sub-opt', 'subopt1'], '') == \
|
||||
['subarg1', 'subarg2']
|
||||
assert choices_without_help(cli,
|
||||
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '-') == ['--bsub-opt']
|
||||
assert choices_without_help(cli,
|
||||
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '') == ['bsubarg1', 'bsubarg2']
|
||||
assert choices_without_help(cli,
|
||||
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt'], '') == \
|
||||
['bsubopt1', 'bsubopt2']
|
||||
assert choices_without_help(cli,
|
||||
['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt', 'bsubopt1', 'bsubarg1'],
|
||||
'') == ['bbsubarg1', 'bbsubarg2']
|
||||
assert choices_without_help(cli,
|
||||
['sub', '--sub-opt', 'subopt1', 'subarg1', 'csub'],
|
||||
'') == ['dsub']
|
||||
assert choices_with_help(cli, ["sub", "subarg1"], "") == [
|
||||
("bsub", "bsub help"),
|
||||
("csub", ""),
|
||||
]
|
||||
assert choices_without_help(cli, ["sub"], "") == ["subarg1", "subarg2"]
|
||||
assert choices_without_help(cli, ["sub", "--sub-opt"], "") == ["subopt1", "subopt2"]
|
||||
assert choices_without_help(cli, ["sub", "--sub-opt", "subopt1"], "") == [
|
||||
"subarg1",
|
||||
"subarg2",
|
||||
]
|
||||
assert choices_without_help(
|
||||
cli, ["sub", "--sub-opt", "subopt1", "subarg1", "bsub"], "-"
|
||||
) == ["--bsub-opt"]
|
||||
assert choices_without_help(
|
||||
cli, ["sub", "--sub-opt", "subopt1", "subarg1", "bsub"], ""
|
||||
) == ["bsubarg1", "bsubarg2"]
|
||||
assert choices_without_help(
|
||||
cli, ["sub", "--sub-opt", "subopt1", "subarg1", "bsub", "--bsub-opt"], ""
|
||||
) == ["bsubopt1", "bsubopt2"]
|
||||
assert choices_without_help(
|
||||
cli,
|
||||
[
|
||||
"sub",
|
||||
"--sub-opt",
|
||||
"subopt1",
|
||||
"subarg1",
|
||||
"bsub",
|
||||
"--bsub-opt",
|
||||
"bsubopt1",
|
||||
"bsubarg1",
|
||||
],
|
||||
"",
|
||||
) == ["bbsubarg1", "bbsubarg2"]
|
||||
assert choices_without_help(
|
||||
cli, ["sub", "--sub-opt", "subopt1", "subarg1", "csub"], ""
|
||||
) == ["dsub"]
|
||||
|
||||
|
||||
def test_chained_multi():
|
||||
|
@ -367,16 +433,16 @@ def test_chained_multi():
|
|||
def esub():
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, ['sub'], '') == ['bsub', 'csub']
|
||||
assert choices_without_help(cli, ['sub'], 'c') == ['csub']
|
||||
assert choices_without_help(cli, ['sub', 'csub'], '') == ['dsub', 'esub']
|
||||
assert choices_without_help(cli, ['sub', 'csub', 'dsub'], '') == ['esub']
|
||||
assert choices_without_help(cli, ["sub"], "") == ["bsub", "csub"]
|
||||
assert choices_without_help(cli, ["sub"], "c") == ["csub"]
|
||||
assert choices_without_help(cli, ["sub", "csub"], "") == ["dsub", "esub"]
|
||||
assert choices_without_help(cli, ["sub", "csub", "dsub"], "") == ["esub"]
|
||||
|
||||
|
||||
def test_hidden():
|
||||
@click.group()
|
||||
@click.option('--name', hidden=True)
|
||||
@click.option('--choices', type=click.Choice([1, 2]), hidden=True)
|
||||
@click.option("--name", hidden=True)
|
||||
@click.option("--choices", type=click.Choice([1, 2]), hidden=True)
|
||||
def cli(name):
|
||||
pass
|
||||
|
||||
|
@ -393,17 +459,42 @@ def test_hidden():
|
|||
pass
|
||||
|
||||
@cli.command(hidden=True)
|
||||
@click.option('--hname')
|
||||
@click.option("--hname")
|
||||
def hsub():
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, [], '--n') == []
|
||||
assert choices_without_help(cli, [], '--c') == []
|
||||
assert choices_without_help(cli, [], "--n") == []
|
||||
assert choices_without_help(cli, [], "--c") == []
|
||||
# If the user exactly types out the hidden param, complete its options.
|
||||
assert choices_without_help(cli, ['--choices'], '') == [1, 2]
|
||||
assert choices_without_help(cli, [], '') == ['asub']
|
||||
assert choices_without_help(cli, [], '') == ['asub']
|
||||
assert choices_without_help(cli, [], 'h') == []
|
||||
assert choices_without_help(cli, ["--choices"], "") == [1, 2]
|
||||
assert choices_without_help(cli, [], "") == ["asub"]
|
||||
assert choices_without_help(cli, [], "") == ["asub"]
|
||||
assert choices_without_help(cli, [], "h") == []
|
||||
# If the user exactly types out the hidden command, complete its subcommands.
|
||||
assert choices_without_help(cli, ['hgroup'], '') == ['hgroupsub']
|
||||
assert choices_without_help(cli, ['hsub'], '--h') == ['--hname']
|
||||
assert choices_without_help(cli, ["hgroup"], "") == ["hgroupsub"]
|
||||
assert choices_without_help(cli, ["hsub"], "--h") == ["--hname"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("args", "part", "expect"),
|
||||
[
|
||||
([], "-", ["--opt"]),
|
||||
(["value"], "--", ["--opt"]),
|
||||
([], "-o", []),
|
||||
(["--opt"], "-o", []),
|
||||
(["--"], "", ["name", "-o", "--opt", "--"]),
|
||||
(["--"], "--o", ["--opt"]),
|
||||
],
|
||||
)
|
||||
def test_args_with_double_dash_complete(args, part, expect):
|
||||
def _complete(ctx, args, incomplete):
|
||||
values = ["name", "-o", "--opt", "--"]
|
||||
return [x for x in values if x.startswith(incomplete)]
|
||||
|
||||
@click.command()
|
||||
@click.option("--opt")
|
||||
@click.argument("args", nargs=-1, autocompletion=_complete)
|
||||
def cli(opt, args):
|
||||
pass
|
||||
|
||||
assert choices_without_help(cli, args, part) == expect
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import click
|
||||
|
||||
|
||||
|
@ -8,443 +9,468 @@ def test_basic_functionality(runner):
|
|||
@click.command()
|
||||
def cli():
|
||||
"""Hello World!"""
|
||||
click.echo('I EXECUTED')
|
||||
click.echo("I EXECUTED")
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert not result.exception
|
||||
assert 'Hello World!' in result.output
|
||||
assert 'Show this message and exit.' in result.output
|
||||
assert "Hello World!" in result.output
|
||||
assert "Show this message and exit." in result.output
|
||||
assert result.exit_code == 0
|
||||
assert 'I EXECUTED' not in result.output
|
||||
assert "I EXECUTED" not in result.output
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert 'I EXECUTED' in result.output
|
||||
assert "I EXECUTED" in result.output
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_repr():
|
||||
@click.command()
|
||||
def command():
|
||||
pass
|
||||
|
||||
@click.group()
|
||||
def group():
|
||||
pass
|
||||
|
||||
@group.command()
|
||||
def subcommand():
|
||||
pass
|
||||
|
||||
assert repr(command) == "<Command command>"
|
||||
assert repr(group) == "<Group group>"
|
||||
assert repr(subcommand) == "<Command subcommand>"
|
||||
|
||||
|
||||
def test_return_values():
|
||||
@click.command()
|
||||
def cli():
|
||||
return 42
|
||||
|
||||
with cli.make_context('foo', []) as ctx:
|
||||
with cli.make_context("foo", []) as ctx:
|
||||
rv = cli.invoke(ctx)
|
||||
assert rv is 42
|
||||
assert rv == 42
|
||||
|
||||
|
||||
def test_basic_group(runner):
|
||||
@click.group()
|
||||
def cli():
|
||||
"""This is the root."""
|
||||
click.echo('ROOT EXECUTED')
|
||||
click.echo("ROOT EXECUTED")
|
||||
|
||||
@cli.command()
|
||||
def subcommand():
|
||||
"""This is a subcommand."""
|
||||
click.echo('SUBCOMMAND EXECUTED')
|
||||
click.echo("SUBCOMMAND EXECUTED")
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert not result.exception
|
||||
assert 'COMMAND [ARGS]...' in result.output
|
||||
assert 'This is the root' in result.output
|
||||
assert 'This is a subcommand.' in result.output
|
||||
assert "COMMAND [ARGS]..." in result.output
|
||||
assert "This is the root" in result.output
|
||||
assert "This is a subcommand." in result.output
|
||||
assert result.exit_code == 0
|
||||
assert 'ROOT EXECUTED' not in result.output
|
||||
assert "ROOT EXECUTED" not in result.output
|
||||
|
||||
result = runner.invoke(cli, ['subcommand'])
|
||||
result = runner.invoke(cli, ["subcommand"])
|
||||
assert not result.exception
|
||||
assert result.exit_code == 0
|
||||
assert 'ROOT EXECUTED' in result.output
|
||||
assert 'SUBCOMMAND EXECUTED' in result.output
|
||||
assert "ROOT EXECUTED" in result.output
|
||||
assert "SUBCOMMAND EXECUTED" in result.output
|
||||
|
||||
|
||||
def test_basic_option(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', default='no value')
|
||||
@click.option("--foo", default="no value")
|
||||
def cli(foo):
|
||||
click.echo('FOO:[%s]' % foo)
|
||||
click.echo(u"FOO:[{}]".format(foo))
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert 'FOO:[no value]' in result.output
|
||||
assert "FOO:[no value]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo=42'])
|
||||
result = runner.invoke(cli, ["--foo=42"])
|
||||
assert not result.exception
|
||||
assert 'FOO:[42]' in result.output
|
||||
assert "FOO:[42]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo'])
|
||||
result = runner.invoke(cli, ["--foo"])
|
||||
assert result.exception
|
||||
assert '--foo option requires an argument' in result.output
|
||||
assert "--foo option requires an argument" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo='])
|
||||
result = runner.invoke(cli, ["--foo="])
|
||||
assert not result.exception
|
||||
assert 'FOO:[]' in result.output
|
||||
assert "FOO:[]" in result.output
|
||||
|
||||
result = runner.invoke(cli, [u'--foo=\N{SNOWMAN}'])
|
||||
result = runner.invoke(cli, [u"--foo=\N{SNOWMAN}"])
|
||||
assert not result.exception
|
||||
assert u'FOO:[\N{SNOWMAN}]' in result.output
|
||||
assert u"FOO:[\N{SNOWMAN}]" in result.output
|
||||
|
||||
|
||||
def test_int_option(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', default=42)
|
||||
@click.option("--foo", default=42)
|
||||
def cli(foo):
|
||||
click.echo('FOO:[%s]' % (foo * 2))
|
||||
click.echo("FOO:[{}]".format(foo * 2))
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert 'FOO:[84]' in result.output
|
||||
assert "FOO:[84]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo=23'])
|
||||
result = runner.invoke(cli, ["--foo=23"])
|
||||
assert not result.exception
|
||||
assert 'FOO:[46]' in result.output
|
||||
assert "FOO:[46]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo=bar'])
|
||||
result = runner.invoke(cli, ["--foo=bar"])
|
||||
assert result.exception
|
||||
assert 'Invalid value for "--foo": bar is not a valid integer' \
|
||||
in result.output
|
||||
assert "Invalid value for '--foo': bar is not a valid integer" in result.output
|
||||
|
||||
|
||||
def test_uuid_option(runner):
|
||||
@click.command()
|
||||
@click.option('--u', default='ba122011-349f-423b-873b-9d6a79c688ab',
|
||||
type=click.UUID)
|
||||
@click.option(
|
||||
"--u", default="ba122011-349f-423b-873b-9d6a79c688ab", type=click.UUID
|
||||
)
|
||||
def cli(u):
|
||||
assert type(u) is uuid.UUID
|
||||
click.echo('U:[%s]' % u)
|
||||
click.echo("U:[{}]".format(u))
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert 'U:[ba122011-349f-423b-873b-9d6a79c688ab]' in result.output
|
||||
assert "U:[ba122011-349f-423b-873b-9d6a79c688ab]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--u=821592c1-c50e-4971-9cd6-e89dc6832f86'])
|
||||
result = runner.invoke(cli, ["--u=821592c1-c50e-4971-9cd6-e89dc6832f86"])
|
||||
assert not result.exception
|
||||
assert 'U:[821592c1-c50e-4971-9cd6-e89dc6832f86]' in result.output
|
||||
assert "U:[821592c1-c50e-4971-9cd6-e89dc6832f86]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--u=bar'])
|
||||
result = runner.invoke(cli, ["--u=bar"])
|
||||
assert result.exception
|
||||
assert 'Invalid value for "--u": bar is not a valid UUID value' \
|
||||
in result.output
|
||||
assert "Invalid value for '--u': bar is not a valid UUID value" in result.output
|
||||
|
||||
|
||||
def test_float_option(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', default=42, type=click.FLOAT)
|
||||
@click.option("--foo", default=42, type=click.FLOAT)
|
||||
def cli(foo):
|
||||
assert type(foo) is float
|
||||
click.echo('FOO:[%s]' % foo)
|
||||
click.echo("FOO:[{}]".format(foo))
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert 'FOO:[42.0]' in result.output
|
||||
assert "FOO:[42.0]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo=23.5'])
|
||||
result = runner.invoke(cli, ["--foo=23.5"])
|
||||
assert not result.exception
|
||||
assert 'FOO:[23.5]' in result.output
|
||||
assert "FOO:[23.5]" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--foo=bar'])
|
||||
result = runner.invoke(cli, ["--foo=bar"])
|
||||
assert result.exception
|
||||
assert 'Invalid value for "--foo": bar is not a valid float' \
|
||||
in result.output
|
||||
assert "Invalid value for '--foo': bar is not a valid float" in result.output
|
||||
|
||||
|
||||
def test_boolean_option(runner):
|
||||
for default in True, False:
|
||||
|
||||
@click.command()
|
||||
@click.option('--with-foo/--without-foo', default=default)
|
||||
@click.option("--with-foo/--without-foo", default=default)
|
||||
def cli(with_foo):
|
||||
click.echo(with_foo)
|
||||
|
||||
result = runner.invoke(cli, ['--with-foo'])
|
||||
result = runner.invoke(cli, ["--with-foo"])
|
||||
assert not result.exception
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cli, ['--without-foo'])
|
||||
assert result.output == "True\n"
|
||||
result = runner.invoke(cli, ["--without-foo"])
|
||||
assert not result.exception
|
||||
assert result.output == 'False\n'
|
||||
assert result.output == "False\n"
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == '%s\n' % default
|
||||
assert result.output == "{}\n".format(default)
|
||||
|
||||
for default in True, False:
|
||||
|
||||
@click.command()
|
||||
@click.option('--flag', is_flag=True, default=default)
|
||||
@click.option("--flag", is_flag=True, default=default)
|
||||
def cli(flag):
|
||||
click.echo(flag)
|
||||
|
||||
result = runner.invoke(cli, ['--flag'])
|
||||
result = runner.invoke(cli, ["--flag"])
|
||||
assert not result.exception
|
||||
assert result.output == '%s\n' % (not default)
|
||||
assert result.output == "{}\n".format(not default)
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == '%s\n' % (default)
|
||||
assert result.output == "{}\n".format(default)
|
||||
|
||||
|
||||
def test_boolean_conversion(runner):
|
||||
for default in True, False:
|
||||
|
||||
@click.command()
|
||||
@click.option('--flag', default=default, type=bool)
|
||||
@click.option("--flag", default=default, type=bool)
|
||||
def cli(flag):
|
||||
click.echo(flag)
|
||||
|
||||
for value in 'true', 't', '1', 'yes', 'y':
|
||||
result = runner.invoke(cli, ['--flag', value])
|
||||
for value in "true", "t", "1", "yes", "y":
|
||||
result = runner.invoke(cli, ["--flag", value])
|
||||
assert not result.exception
|
||||
assert result.output == 'True\n'
|
||||
assert result.output == "True\n"
|
||||
|
||||
for value in 'false', 'f', '0', 'no', 'n':
|
||||
result = runner.invoke(cli, ['--flag', value])
|
||||
for value in "false", "f", "0", "no", "n":
|
||||
result = runner.invoke(cli, ["--flag", value])
|
||||
assert not result.exception
|
||||
assert result.output == 'False\n'
|
||||
assert result.output == "False\n"
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == '%s\n' % default
|
||||
assert result.output == "{}\n".format(default)
|
||||
|
||||
|
||||
def test_file_option(runner):
|
||||
@click.command()
|
||||
@click.option('--file', type=click.File('w'))
|
||||
@click.option("--file", type=click.File("w"))
|
||||
def input(file):
|
||||
file.write('Hello World!\n')
|
||||
file.write("Hello World!\n")
|
||||
|
||||
@click.command()
|
||||
@click.option('--file', type=click.File('r'))
|
||||
@click.option("--file", type=click.File("r"))
|
||||
def output(file):
|
||||
click.echo(file.read())
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result_in = runner.invoke(input, ['--file=example.txt'])
|
||||
result_out = runner.invoke(output, ['--file=example.txt'])
|
||||
result_in = runner.invoke(input, ["--file=example.txt"])
|
||||
result_out = runner.invoke(output, ["--file=example.txt"])
|
||||
|
||||
assert not result_in.exception
|
||||
assert result_in.output == ''
|
||||
assert result_in.output == ""
|
||||
assert not result_out.exception
|
||||
assert result_out.output == 'Hello World!\n\n'
|
||||
assert result_out.output == "Hello World!\n\n"
|
||||
|
||||
|
||||
def test_file_lazy_mode(runner):
|
||||
do_io = False
|
||||
|
||||
@click.command()
|
||||
@click.option('--file', type=click.File('w'))
|
||||
@click.option("--file", type=click.File("w"))
|
||||
def input(file):
|
||||
if do_io:
|
||||
file.write('Hello World!\n')
|
||||
file.write("Hello World!\n")
|
||||
|
||||
@click.command()
|
||||
@click.option('--file', type=click.File('r'))
|
||||
@click.option("--file", type=click.File("r"))
|
||||
def output(file):
|
||||
pass
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
os.mkdir('example.txt')
|
||||
os.mkdir("example.txt")
|
||||
|
||||
do_io = True
|
||||
result_in = runner.invoke(input, ['--file=example.txt'])
|
||||
result_in = runner.invoke(input, ["--file=example.txt"])
|
||||
assert result_in.exit_code == 1
|
||||
|
||||
do_io = False
|
||||
result_in = runner.invoke(input, ['--file=example.txt'])
|
||||
result_in = runner.invoke(input, ["--file=example.txt"])
|
||||
assert result_in.exit_code == 0
|
||||
|
||||
result_out = runner.invoke(output, ['--file=example.txt'])
|
||||
result_out = runner.invoke(output, ["--file=example.txt"])
|
||||
assert result_out.exception
|
||||
|
||||
@click.command()
|
||||
@click.option('--file', type=click.File('w', lazy=False))
|
||||
@click.option("--file", type=click.File("w", lazy=False))
|
||||
def input_non_lazy(file):
|
||||
file.write('Hello World!\n')
|
||||
file.write("Hello World!\n")
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
os.mkdir('example.txt')
|
||||
result_in = runner.invoke(input_non_lazy, ['--file=example.txt'])
|
||||
os.mkdir("example.txt")
|
||||
result_in = runner.invoke(input_non_lazy, ["--file=example.txt"])
|
||||
assert result_in.exit_code == 2
|
||||
assert 'Invalid value for "--file": Could not open file: example.txt' \
|
||||
assert (
|
||||
"Invalid value for '--file': Could not open file: example.txt"
|
||||
in result_in.output
|
||||
)
|
||||
|
||||
|
||||
def test_path_option(runner):
|
||||
@click.command()
|
||||
@click.option('-O', type=click.Path(file_okay=False, exists=True,
|
||||
writable=True))
|
||||
@click.option("-O", type=click.Path(file_okay=False, exists=True, writable=True))
|
||||
def write_to_dir(o):
|
||||
with open(os.path.join(o, 'foo.txt'), 'wb') as f:
|
||||
f.write(b'meh\n')
|
||||
with open(os.path.join(o, "foo.txt"), "wb") as f:
|
||||
f.write(b"meh\n")
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
os.mkdir('test')
|
||||
os.mkdir("test")
|
||||
|
||||
result = runner.invoke(write_to_dir, ['-O', 'test'])
|
||||
result = runner.invoke(write_to_dir, ["-O", "test"])
|
||||
assert not result.exception
|
||||
|
||||
with open('test/foo.txt', 'rb') as f:
|
||||
assert f.read() == b'meh\n'
|
||||
with open("test/foo.txt", "rb") as f:
|
||||
assert f.read() == b"meh\n"
|
||||
|
||||
result = runner.invoke(write_to_dir, ['-O', 'test/foo.txt'])
|
||||
assert 'Invalid value for "-O": Directory "test/foo.txt" is a file.' \
|
||||
in result.output
|
||||
result = runner.invoke(write_to_dir, ["-O", "test/foo.txt"])
|
||||
assert "is a file" in result.output
|
||||
|
||||
@click.command()
|
||||
@click.option('-f', type=click.Path(exists=True))
|
||||
@click.option("-f", type=click.Path(exists=True))
|
||||
def showtype(f):
|
||||
click.echo('is_file=%s' % os.path.isfile(f))
|
||||
click.echo('is_dir=%s' % os.path.isdir(f))
|
||||
click.echo("is_file={}".format(os.path.isfile(f)))
|
||||
click.echo("is_dir={}".format(os.path.isdir(f)))
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(showtype, ['-f', 'xxx'])
|
||||
assert 'Error: Invalid value for "-f": Path "xxx" does not exist' \
|
||||
in result.output
|
||||
result = runner.invoke(showtype, ["-f", "xxx"])
|
||||
assert "does not exist" in result.output
|
||||
|
||||
result = runner.invoke(showtype, ['-f', '.'])
|
||||
assert 'is_file=False' in result.output
|
||||
assert 'is_dir=True' in result.output
|
||||
result = runner.invoke(showtype, ["-f", "."])
|
||||
assert "is_file=False" in result.output
|
||||
assert "is_dir=True" in result.output
|
||||
|
||||
@click.command()
|
||||
@click.option('-f', type=click.Path())
|
||||
@click.option("-f", type=click.Path())
|
||||
def exists(f):
|
||||
click.echo('exists=%s' % os.path.exists(f))
|
||||
click.echo("exists={}".format(os.path.exists(f)))
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(exists, ['-f', 'xxx'])
|
||||
assert 'exists=False' in result.output
|
||||
result = runner.invoke(exists, ["-f", "xxx"])
|
||||
assert "exists=False" in result.output
|
||||
|
||||
result = runner.invoke(exists, ['-f', '.'])
|
||||
assert 'exists=True' in result.output
|
||||
result = runner.invoke(exists, ["-f", "."])
|
||||
assert "exists=True" in result.output
|
||||
|
||||
|
||||
def test_choice_option(runner):
|
||||
@click.command()
|
||||
@click.option('--method', type=click.Choice(['foo', 'bar', 'baz']))
|
||||
@click.option("--method", type=click.Choice(["foo", "bar", "baz"]))
|
||||
def cli(method):
|
||||
click.echo(method)
|
||||
|
||||
result = runner.invoke(cli, ['--method=foo'])
|
||||
result = runner.invoke(cli, ["--method=foo"])
|
||||
assert not result.exception
|
||||
assert result.output == 'foo\n'
|
||||
assert result.output == "foo\n"
|
||||
|
||||
result = runner.invoke(cli, ['--method=meh'])
|
||||
result = runner.invoke(cli, ["--method=meh"])
|
||||
assert result.exit_code == 2
|
||||
assert 'Invalid value for "--method": invalid choice: meh. ' \
|
||||
'(choose from foo, bar, baz)' in result.output
|
||||
assert (
|
||||
"Invalid value for '--method': invalid choice: meh."
|
||||
" (choose from foo, bar, baz)" in result.output
|
||||
)
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert '--method [foo|bar|baz]' in result.output
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert "--method [foo|bar|baz]" in result.output
|
||||
|
||||
|
||||
def test_datetime_option_default(runner):
|
||||
|
||||
@click.command()
|
||||
@click.option('--start_date', type=click.DateTime())
|
||||
@click.option("--start_date", type=click.DateTime())
|
||||
def cli(start_date):
|
||||
click.echo(start_date.strftime('%Y-%m-%dT%H:%M:%S'))
|
||||
click.echo(start_date.strftime("%Y-%m-%dT%H:%M:%S"))
|
||||
|
||||
result = runner.invoke(cli, ['--start_date=2015-09-29'])
|
||||
result = runner.invoke(cli, ["--start_date=2015-09-29"])
|
||||
assert not result.exception
|
||||
assert result.output == '2015-09-29T00:00:00\n'
|
||||
assert result.output == "2015-09-29T00:00:00\n"
|
||||
|
||||
result = runner.invoke(cli, ['--start_date=2015-09-29T09:11:22'])
|
||||
result = runner.invoke(cli, ["--start_date=2015-09-29T09:11:22"])
|
||||
assert not result.exception
|
||||
assert result.output == '2015-09-29T09:11:22\n'
|
||||
assert result.output == "2015-09-29T09:11:22\n"
|
||||
|
||||
result = runner.invoke(cli, ['--start_date=2015-09'])
|
||||
result = runner.invoke(cli, ["--start_date=2015-09"])
|
||||
assert result.exit_code == 2
|
||||
assert ('Invalid value for "--start_date": '
|
||||
'invalid datetime format: 2015-09. '
|
||||
'(choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S)'
|
||||
) in result.output
|
||||
assert (
|
||||
"Invalid value for '--start_date':"
|
||||
" invalid datetime format: 2015-09."
|
||||
" (choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S)"
|
||||
) in result.output
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert '--start_date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]' in result.output
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert (
|
||||
"--start_date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]" in result.output
|
||||
)
|
||||
|
||||
|
||||
def test_datetime_option_custom(runner):
|
||||
@click.command()
|
||||
@click.option('--start_date',
|
||||
type=click.DateTime(formats=['%A %B %d, %Y']))
|
||||
@click.option("--start_date", type=click.DateTime(formats=["%A %B %d, %Y"]))
|
||||
def cli(start_date):
|
||||
click.echo(start_date.strftime('%Y-%m-%dT%H:%M:%S'))
|
||||
click.echo(start_date.strftime("%Y-%m-%dT%H:%M:%S"))
|
||||
|
||||
result = runner.invoke(cli, ['--start_date=Wednesday June 05, 2010'])
|
||||
result = runner.invoke(cli, ["--start_date=Wednesday June 05, 2010"])
|
||||
assert not result.exception
|
||||
assert result.output == '2010-06-05T00:00:00\n'
|
||||
assert result.output == "2010-06-05T00:00:00\n"
|
||||
|
||||
|
||||
def test_int_range_option(runner):
|
||||
@click.command()
|
||||
@click.option('--x', type=click.IntRange(0, 5))
|
||||
@click.option("--x", type=click.IntRange(0, 5))
|
||||
def cli(x):
|
||||
click.echo(x)
|
||||
|
||||
result = runner.invoke(cli, ['--x=5'])
|
||||
result = runner.invoke(cli, ["--x=5"])
|
||||
assert not result.exception
|
||||
assert result.output == '5\n'
|
||||
assert result.output == "5\n"
|
||||
|
||||
result = runner.invoke(cli, ['--x=6'])
|
||||
result = runner.invoke(cli, ["--x=6"])
|
||||
assert result.exit_code == 2
|
||||
assert 'Invalid value for "--x": 6 is not in the valid range of 0 to 5.\n' \
|
||||
assert (
|
||||
"Invalid value for '--x': 6 is not in the valid range of 0 to 5.\n"
|
||||
in result.output
|
||||
)
|
||||
|
||||
@click.command()
|
||||
@click.option('--x', type=click.IntRange(0, 5, clamp=True))
|
||||
@click.option("--x", type=click.IntRange(0, 5, clamp=True))
|
||||
def clamp(x):
|
||||
click.echo(x)
|
||||
|
||||
result = runner.invoke(clamp, ['--x=5'])
|
||||
result = runner.invoke(clamp, ["--x=5"])
|
||||
assert not result.exception
|
||||
assert result.output == '5\n'
|
||||
assert result.output == "5\n"
|
||||
|
||||
result = runner.invoke(clamp, ['--x=6'])
|
||||
result = runner.invoke(clamp, ["--x=6"])
|
||||
assert not result.exception
|
||||
assert result.output == '5\n'
|
||||
assert result.output == "5\n"
|
||||
|
||||
result = runner.invoke(clamp, ['--x=-1'])
|
||||
result = runner.invoke(clamp, ["--x=-1"])
|
||||
assert not result.exception
|
||||
assert result.output == '0\n'
|
||||
assert result.output == "0\n"
|
||||
|
||||
|
||||
def test_float_range_option(runner):
|
||||
@click.command()
|
||||
@click.option('--x', type=click.FloatRange(0, 5))
|
||||
@click.option("--x", type=click.FloatRange(0, 5))
|
||||
def cli(x):
|
||||
click.echo(x)
|
||||
|
||||
result = runner.invoke(cli, ['--x=5.0'])
|
||||
result = runner.invoke(cli, ["--x=5.0"])
|
||||
assert not result.exception
|
||||
assert result.output == '5.0\n'
|
||||
assert result.output == "5.0\n"
|
||||
|
||||
result = runner.invoke(cli, ['--x=6.0'])
|
||||
result = runner.invoke(cli, ["--x=6.0"])
|
||||
assert result.exit_code == 2
|
||||
assert 'Invalid value for "--x": 6.0 is not in the valid range of 0 to 5.\n' \
|
||||
assert (
|
||||
"Invalid value for '--x': 6.0 is not in the valid range of 0 to 5.\n"
|
||||
in result.output
|
||||
)
|
||||
|
||||
@click.command()
|
||||
@click.option('--x', type=click.FloatRange(0, 5, clamp=True))
|
||||
@click.option("--x", type=click.FloatRange(0, 5, clamp=True))
|
||||
def clamp(x):
|
||||
click.echo(x)
|
||||
|
||||
result = runner.invoke(clamp, ['--x=5.0'])
|
||||
result = runner.invoke(clamp, ["--x=5.0"])
|
||||
assert not result.exception
|
||||
assert result.output == '5.0\n'
|
||||
assert result.output == "5.0\n"
|
||||
|
||||
result = runner.invoke(clamp, ['--x=6.0'])
|
||||
result = runner.invoke(clamp, ["--x=6.0"])
|
||||
assert not result.exception
|
||||
assert result.output == '5\n'
|
||||
assert result.output == "5\n"
|
||||
|
||||
result = runner.invoke(clamp, ['--x=-1.0'])
|
||||
result = runner.invoke(clamp, ["--x=-1.0"])
|
||||
assert not result.exception
|
||||
assert result.output == '0\n'
|
||||
assert result.output == "0\n"
|
||||
|
||||
|
||||
def test_required_option(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', required=True)
|
||||
@click.option("--foo", required=True)
|
||||
def cli(foo):
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 2
|
||||
assert 'Missing option "--foo"' in result.output
|
||||
assert "Missing option '--foo'" in result.output
|
||||
|
||||
|
||||
def test_evaluation_order(runner):
|
||||
|
@ -455,52 +481,50 @@ def test_evaluation_order(runner):
|
|||
return value
|
||||
|
||||
@click.command()
|
||||
@click.option('--missing', default='missing',
|
||||
is_eager=False, callback=memo)
|
||||
@click.option('--eager-flag1', flag_value='eager1',
|
||||
is_eager=True, callback=memo)
|
||||
@click.option('--eager-flag2', flag_value='eager2',
|
||||
is_eager=True, callback=memo)
|
||||
@click.option('--eager-flag3', flag_value='eager3',
|
||||
is_eager=True, callback=memo)
|
||||
@click.option('--normal-flag1', flag_value='normal1',
|
||||
is_eager=False, callback=memo)
|
||||
@click.option('--normal-flag2', flag_value='normal2',
|
||||
is_eager=False, callback=memo)
|
||||
@click.option('--normal-flag3', flag_value='normal3',
|
||||
is_eager=False, callback=memo)
|
||||
@click.option("--missing", default="missing", is_eager=False, callback=memo)
|
||||
@click.option("--eager-flag1", flag_value="eager1", is_eager=True, callback=memo)
|
||||
@click.option("--eager-flag2", flag_value="eager2", is_eager=True, callback=memo)
|
||||
@click.option("--eager-flag3", flag_value="eager3", is_eager=True, callback=memo)
|
||||
@click.option("--normal-flag1", flag_value="normal1", is_eager=False, callback=memo)
|
||||
@click.option("--normal-flag2", flag_value="normal2", is_eager=False, callback=memo)
|
||||
@click.option("--normal-flag3", flag_value="normal3", is_eager=False, callback=memo)
|
||||
def cli(**x):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, ['--eager-flag2',
|
||||
'--eager-flag1',
|
||||
'--normal-flag2',
|
||||
'--eager-flag3',
|
||||
'--normal-flag3',
|
||||
'--normal-flag3',
|
||||
'--normal-flag1',
|
||||
'--normal-flag1'])
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"--eager-flag2",
|
||||
"--eager-flag1",
|
||||
"--normal-flag2",
|
||||
"--eager-flag3",
|
||||
"--normal-flag3",
|
||||
"--normal-flag3",
|
||||
"--normal-flag1",
|
||||
"--normal-flag1",
|
||||
],
|
||||
)
|
||||
assert not result.exception
|
||||
assert called == [
|
||||
'eager2',
|
||||
'eager1',
|
||||
'eager3',
|
||||
'normal2',
|
||||
'normal3',
|
||||
'normal1',
|
||||
'missing',
|
||||
"eager2",
|
||||
"eager1",
|
||||
"eager3",
|
||||
"normal2",
|
||||
"normal3",
|
||||
"normal1",
|
||||
"missing",
|
||||
]
|
||||
|
||||
|
||||
def test_hidden_option(runner):
|
||||
@click.command()
|
||||
@click.option('--nope', hidden=True)
|
||||
@click.option("--nope", hidden=True)
|
||||
def cli(nope):
|
||||
click.echo(nope)
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert '--nope' not in result.output
|
||||
assert "--nope" not in result.output
|
||||
|
||||
|
||||
def test_hidden_command(runner):
|
||||
|
@ -512,9 +536,9 @@ def test_hidden_command(runner):
|
|||
def nope():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert 'nope' not in result.output
|
||||
assert "nope" not in result.output
|
||||
|
||||
|
||||
def test_hidden_group(runner):
|
||||
|
@ -530,7 +554,7 @@ def test_hidden_group(runner):
|
|||
def nope():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert 'subgroup' not in result.output
|
||||
assert 'nope' not in result.output
|
||||
assert "subgroup" not in result.output
|
||||
assert "nope" not in result.output
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import sys
|
||||
import click
|
||||
|
||||
import pytest
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def debug():
|
||||
click.echo('%s=%s' % (
|
||||
sys._getframe(1).f_code.co_name,
|
||||
'|'.join(click.get_current_context().args),
|
||||
))
|
||||
click.echo(
|
||||
"{}={}".format(
|
||||
sys._getframe(1).f_code.co_name, "|".join(click.get_current_context().args)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_basic_chaining(runner):
|
||||
|
@ -15,20 +18,20 @@ def test_basic_chaining(runner):
|
|||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command('sdist')
|
||||
@cli.command("sdist")
|
||||
def sdist():
|
||||
click.echo('sdist called')
|
||||
click.echo("sdist called")
|
||||
|
||||
@cli.command('bdist')
|
||||
@cli.command("bdist")
|
||||
def bdist():
|
||||
click.echo('bdist called')
|
||||
click.echo("bdist called")
|
||||
|
||||
result = runner.invoke(cli, ['bdist', 'sdist', 'bdist'])
|
||||
result = runner.invoke(cli, ["bdist", "sdist", "bdist"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'bdist called',
|
||||
'sdist called',
|
||||
'bdist called',
|
||||
"bdist called",
|
||||
"sdist called",
|
||||
"bdist called",
|
||||
]
|
||||
|
||||
|
||||
|
@ -38,32 +41,32 @@ def test_chaining_help(runner):
|
|||
"""ROOT HELP"""
|
||||
pass
|
||||
|
||||
@cli.command('sdist')
|
||||
@cli.command("sdist")
|
||||
def sdist():
|
||||
"""SDIST HELP"""
|
||||
click.echo('sdist called')
|
||||
click.echo("sdist called")
|
||||
|
||||
@cli.command('bdist')
|
||||
@cli.command("bdist")
|
||||
def bdist():
|
||||
"""BDIST HELP"""
|
||||
click.echo('bdist called')
|
||||
click.echo("bdist called")
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert not result.exception
|
||||
assert 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...' in result.output
|
||||
assert 'ROOT HELP' in result.output
|
||||
assert "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." in result.output
|
||||
assert "ROOT HELP" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['sdist', '--help'])
|
||||
result = runner.invoke(cli, ["sdist", "--help"])
|
||||
assert not result.exception
|
||||
assert 'SDIST HELP' in result.output
|
||||
assert "SDIST HELP" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['bdist', '--help'])
|
||||
result = runner.invoke(cli, ["bdist", "--help"])
|
||||
assert not result.exception
|
||||
assert 'BDIST HELP' in result.output
|
||||
assert "BDIST HELP" in result.output
|
||||
|
||||
result = runner.invoke(cli, ['bdist', 'sdist', '--help'])
|
||||
result = runner.invoke(cli, ["bdist", "sdist", "--help"])
|
||||
assert not result.exception
|
||||
assert 'SDIST HELP' in result.output
|
||||
assert "SDIST HELP" in result.output
|
||||
|
||||
|
||||
def test_chaining_with_options(runner):
|
||||
|
@ -71,22 +74,19 @@ def test_chaining_with_options(runner):
|
|||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command('sdist')
|
||||
@click.option('--format')
|
||||
@cli.command("sdist")
|
||||
@click.option("--format")
|
||||
def sdist(format):
|
||||
click.echo('sdist called %s' % format)
|
||||
click.echo("sdist called {}".format(format))
|
||||
|
||||
@cli.command('bdist')
|
||||
@click.option('--format')
|
||||
@cli.command("bdist")
|
||||
@click.option("--format")
|
||||
def bdist(format):
|
||||
click.echo('bdist called %s' % format)
|
||||
click.echo("bdist called {}".format(format))
|
||||
|
||||
result = runner.invoke(cli, ['bdist', '--format=1', 'sdist', '--format=2'])
|
||||
result = runner.invoke(cli, ["bdist", "--format=1", "sdist", "--format=2"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'bdist called 1',
|
||||
'sdist called 2',
|
||||
]
|
||||
assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
|
||||
|
||||
|
||||
def test_chaining_with_arguments(runner):
|
||||
|
@ -94,73 +94,62 @@ def test_chaining_with_arguments(runner):
|
|||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command('sdist')
|
||||
@click.argument('format')
|
||||
@cli.command("sdist")
|
||||
@click.argument("format")
|
||||
def sdist(format):
|
||||
click.echo('sdist called %s' % format)
|
||||
click.echo("sdist called {}".format(format))
|
||||
|
||||
@cli.command('bdist')
|
||||
@click.argument('format')
|
||||
@cli.command("bdist")
|
||||
@click.argument("format")
|
||||
def bdist(format):
|
||||
click.echo('bdist called %s' % format)
|
||||
click.echo("bdist called {}".format(format))
|
||||
|
||||
result = runner.invoke(cli, ['bdist', '1', 'sdist', '2'])
|
||||
result = runner.invoke(cli, ["bdist", "1", "sdist", "2"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'bdist called 1',
|
||||
'sdist called 2',
|
||||
]
|
||||
assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
|
||||
|
||||
|
||||
def test_pipeline(runner):
|
||||
@click.group(chain=True, invoke_without_command=True)
|
||||
@click.option('-i', '--input', type=click.File('r'))
|
||||
@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)
|
||||
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')
|
||||
@cli.command("uppercase")
|
||||
def make_uppercase():
|
||||
def processor(iterator):
|
||||
for line in iterator:
|
||||
yield line.upper()
|
||||
|
||||
return processor
|
||||
|
||||
@cli.command('strip')
|
||||
@cli.command("strip")
|
||||
def make_strip():
|
||||
def processor(iterator):
|
||||
for line in iterator:
|
||||
yield line.strip()
|
||||
|
||||
return processor
|
||||
|
||||
result = runner.invoke(cli, ['-i', '-'], input='foo\nbar')
|
||||
result = runner.invoke(cli, ["-i", "-"], input="foo\nbar")
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'foo',
|
||||
'bar',
|
||||
]
|
||||
assert result.output.splitlines() == ["foo", "bar"]
|
||||
|
||||
result = runner.invoke(cli, ['-i', '-', 'strip'], input='foo \n bar')
|
||||
result = runner.invoke(cli, ["-i", "-", "strip"], input="foo \n bar")
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'foo',
|
||||
'bar',
|
||||
]
|
||||
assert result.output.splitlines() == ["foo", "bar"]
|
||||
|
||||
result = runner.invoke(cli, ['-i', '-', 'strip', 'uppercase'],
|
||||
input='foo \n bar')
|
||||
result = runner.invoke(cli, ["-i", "-", "strip", "uppercase"], input="foo \n bar")
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'FOO',
|
||||
'BAR',
|
||||
]
|
||||
assert result.output.splitlines() == ["FOO", "BAR"]
|
||||
|
||||
|
||||
def test_args_and_chain(runner):
|
||||
|
@ -180,44 +169,38 @@ def test_args_and_chain(runner):
|
|||
def c():
|
||||
debug()
|
||||
|
||||
result = runner.invoke(cli, ['a', 'b', 'c'])
|
||||
result = runner.invoke(cli, ["a", "b", "c"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'cli=',
|
||||
'a=',
|
||||
'b=',
|
||||
'c=',
|
||||
]
|
||||
assert result.output.splitlines() == ["cli=", "a=", "b=", "c="]
|
||||
|
||||
|
||||
def test_multicommand_arg_behavior(runner):
|
||||
with pytest.raises(RuntimeError):
|
||||
|
||||
@click.group(chain=True)
|
||||
@click.argument('forbidden', required=False)
|
||||
@click.argument("forbidden", required=False)
|
||||
def bad_cli():
|
||||
pass
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
|
||||
@click.group(chain=True)
|
||||
@click.argument('forbidden', nargs=-1)
|
||||
@click.argument("forbidden", nargs=-1)
|
||||
def bad_cli2():
|
||||
pass
|
||||
|
||||
@click.group(chain=True)
|
||||
@click.argument('arg')
|
||||
@click.argument("arg")
|
||||
def cli(arg):
|
||||
click.echo('cli:%s' % arg)
|
||||
click.echo("cli:{}".format(arg))
|
||||
|
||||
@cli.command()
|
||||
def a():
|
||||
click.echo('a')
|
||||
click.echo("a")
|
||||
|
||||
result = runner.invoke(cli, ['foo', 'a'])
|
||||
result = runner.invoke(cli, ["foo", "a"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'cli:foo',
|
||||
'a',
|
||||
]
|
||||
assert result.output.splitlines() == ["cli:foo", "a"]
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
|
@ -242,11 +225,6 @@ def test_multicommand_chaining(runner):
|
|||
def l1b():
|
||||
debug()
|
||||
|
||||
result = runner.invoke(cli, ['l1a', 'l2a', 'l1b'])
|
||||
result = runner.invoke(cli, ["l1a", "l2a", "l1b"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'cli=',
|
||||
'l1a=',
|
||||
'l2a=',
|
||||
'l1b=',
|
||||
]
|
||||
assert result.output.splitlines() == ["cli=", "l1a=", "l2a=", "l1b="]
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import re
|
||||
|
||||
import click
|
||||
import pytest
|
||||
|
||||
|
||||
def test_other_command_invoke(runner):
|
||||
|
@ -12,33 +11,33 @@ def test_other_command_invoke(runner):
|
|||
return ctx.invoke(other_cmd, arg=42)
|
||||
|
||||
@click.command()
|
||||
@click.argument('arg', type=click.INT)
|
||||
@click.argument("arg", type=click.INT)
|
||||
def other_cmd(arg):
|
||||
click.echo(arg)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == '42\n'
|
||||
assert result.output == "42\n"
|
||||
|
||||
|
||||
def test_other_command_forward(runner):
|
||||
cli = click.Group()
|
||||
|
||||
@cli.command()
|
||||
@click.option('--count', default=1)
|
||||
@click.option("--count", default=1)
|
||||
def test(count):
|
||||
click.echo('Count: %d' % count)
|
||||
click.echo("Count: {:d}".format(count))
|
||||
|
||||
@cli.command()
|
||||
@click.option('--count', default=1)
|
||||
@click.option("--count", default=1)
|
||||
@click.pass_context
|
||||
def dist(ctx, count):
|
||||
ctx.forward(test)
|
||||
ctx.invoke(test, count=42)
|
||||
|
||||
result = runner.invoke(cli, ['dist'])
|
||||
result = runner.invoke(cli, ["dist"])
|
||||
assert not result.exception
|
||||
assert result.output == 'Count: 1\nCount: 42\n'
|
||||
assert result.output == "Count: 1\nCount: 42\n"
|
||||
|
||||
|
||||
def test_auto_shorthelp(runner):
|
||||
|
@ -59,13 +58,28 @@ def test_auto_shorthelp(runner):
|
|||
"""This is a long text that is too long to show as short help
|
||||
and will be truncated instead."""
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert re.search(
|
||||
r'Commands:\n\s+'
|
||||
r'long\s+This is a long text that is too long to show as short help\.\.\.\n\s+'
|
||||
r'short\s+This is a short text\.\n\s+'
|
||||
r'special-chars\s+Login and store the token in ~/.netrc\.\s*',
|
||||
result.output) is not None
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert (
|
||||
re.search(
|
||||
r"Commands:\n\s+"
|
||||
r"long\s+This is a long text that is too long to show as short help"
|
||||
r"\.\.\.\n\s+"
|
||||
r"short\s+This is a short text\.\n\s+"
|
||||
r"special-chars\s+Login and store the token in ~/.netrc\.\s*",
|
||||
result.output,
|
||||
)
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
def test_no_args_is_help(runner):
|
||||
@click.command(no_args_is_help=True)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert "Show this message and exit." in result.output
|
||||
|
||||
|
||||
def test_default_maps(runner):
|
||||
|
@ -74,43 +88,41 @@ def test_default_maps(runner):
|
|||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--name', default='normal')
|
||||
@click.option("--name", default="normal")
|
||||
def foo(name):
|
||||
click.echo(name)
|
||||
|
||||
result = runner.invoke(cli, ['foo'], default_map={
|
||||
'foo': {'name': 'changed'}
|
||||
})
|
||||
result = runner.invoke(cli, ["foo"], default_map={"foo": {"name": "changed"}})
|
||||
|
||||
assert not result.exception
|
||||
assert result.output == 'changed\n'
|
||||
assert result.output == "changed\n"
|
||||
|
||||
|
||||
def test_group_with_args(runner):
|
||||
@click.group()
|
||||
@click.argument('obj')
|
||||
@click.argument("obj")
|
||||
def cli(obj):
|
||||
click.echo('obj=%s' % obj)
|
||||
click.echo("obj={}".format(obj))
|
||||
|
||||
@cli.command()
|
||||
def move():
|
||||
click.echo('move')
|
||||
click.echo("move")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert 'Show this message and exit.' in result.output
|
||||
assert "Show this message and exit." in result.output
|
||||
|
||||
result = runner.invoke(cli, ['obj1'])
|
||||
result = runner.invoke(cli, ["obj1"])
|
||||
assert result.exit_code == 2
|
||||
assert 'Error: Missing command.' in result.output
|
||||
assert "Error: Missing command." in result.output
|
||||
|
||||
result = runner.invoke(cli, ['obj1', '--help'])
|
||||
result = runner.invoke(cli, ["obj1", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert 'Show this message and exit.' in result.output
|
||||
assert "Show this message and exit." in result.output
|
||||
|
||||
result = runner.invoke(cli, ['obj1', 'move'])
|
||||
result = runner.invoke(cli, ["obj1", "move"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'obj=obj1\nmove\n'
|
||||
assert result.output == "obj=obj1\nmove\n"
|
||||
|
||||
|
||||
def test_base_command(runner):
|
||||
|
@ -121,7 +133,6 @@ def test_base_command(runner):
|
|||
pass
|
||||
|
||||
class OptParseCommand(click.BaseCommand):
|
||||
|
||||
def __init__(self, name, parser, callback):
|
||||
click.BaseCommand.__init__(self, name)
|
||||
self.parser = parser
|
||||
|
@ -144,58 +155,67 @@ def test_base_command(runner):
|
|||
def invoke(self, ctx):
|
||||
ctx.invoke(self.callback, ctx.args, **ctx.params)
|
||||
|
||||
parser = optparse.OptionParser(usage='Usage: foo test [OPTIONS]')
|
||||
parser.add_option("-f", "--file", dest="filename",
|
||||
help="write report to FILE", metavar="FILE")
|
||||
parser.add_option("-q", "--quiet",
|
||||
action="store_false", dest="verbose", default=True,
|
||||
help="don't print status messages to stdout")
|
||||
parser = optparse.OptionParser(usage="Usage: foo test [OPTIONS]")
|
||||
parser.add_option(
|
||||
"-f", "--file", dest="filename", help="write report to FILE", metavar="FILE"
|
||||
)
|
||||
parser.add_option(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_false",
|
||||
dest="verbose",
|
||||
default=True,
|
||||
help="don't print status messages to stdout",
|
||||
)
|
||||
|
||||
def test_callback(args, filename, verbose):
|
||||
click.echo(' '.join(args))
|
||||
click.echo(" ".join(args))
|
||||
click.echo(filename)
|
||||
click.echo(verbose)
|
||||
cli.add_command(OptParseCommand('test', parser, test_callback))
|
||||
|
||||
result = runner.invoke(cli, ['test', '-f', 'test.txt', '-q',
|
||||
'whatever.txt', 'whateverelse.txt'])
|
||||
cli.add_command(OptParseCommand("test", parser, test_callback))
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["test", "-f", "test.txt", "-q", "whatever.txt", "whateverelse.txt"]
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'whatever.txt whateverelse.txt',
|
||||
'test.txt',
|
||||
'False',
|
||||
"whatever.txt whateverelse.txt",
|
||||
"test.txt",
|
||||
"False",
|
||||
]
|
||||
|
||||
result = runner.invoke(cli, ['test', '--help'])
|
||||
result = runner.invoke(cli, ["test", "--help"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: foo test [OPTIONS]',
|
||||
'',
|
||||
'Options:',
|
||||
' -h, --help show this help message and exit',
|
||||
' -f FILE, --file=FILE write report to FILE',
|
||||
' -q, --quiet don\'t print status messages to stdout',
|
||||
"Usage: foo test [OPTIONS]",
|
||||
"",
|
||||
"Options:",
|
||||
" -h, --help show this help message and exit",
|
||||
" -f FILE, --file=FILE write report to FILE",
|
||||
" -q, --quiet don't print status messages to stdout",
|
||||
]
|
||||
|
||||
|
||||
def test_object_propagation(runner):
|
||||
for chain in False, True:
|
||||
|
||||
@click.group(chain=chain)
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
@click.option("--debug/--no-debug", default=False)
|
||||
@click.pass_context
|
||||
def cli(ctx, debug):
|
||||
if ctx.obj is None:
|
||||
ctx.obj = {}
|
||||
ctx.obj['DEBUG'] = 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'))
|
||||
click.echo("Debug is {}".format("on" if ctx.obj["DEBUG"] else "off"))
|
||||
|
||||
result = runner.invoke(cli, ['sync'])
|
||||
result = runner.invoke(cli, ["sync"])
|
||||
assert result.exception is None
|
||||
assert result.output == 'Debug is off\n'
|
||||
assert result.output == "Debug is off\n"
|
||||
|
||||
|
||||
def test_other_command_invoke_with_defaults(runner):
|
||||
|
@ -205,15 +225,15 @@ def test_other_command_invoke_with_defaults(runner):
|
|||
return ctx.invoke(other_cmd)
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', type=click.INT, default=42)
|
||||
@click.option("--foo", type=click.INT, default=42)
|
||||
@click.pass_context
|
||||
def other_cmd(ctx, foo):
|
||||
assert ctx.info_name == 'other-cmd'
|
||||
assert ctx.info_name == "other-cmd"
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == '42\n'
|
||||
assert result.output == "42\n"
|
||||
|
||||
|
||||
def test_invoked_subcommand(runner):
|
||||
|
@ -221,39 +241,37 @@ def test_invoked_subcommand(runner):
|
|||
@click.pass_context
|
||||
def cli(ctx):
|
||||
if ctx.invoked_subcommand is None:
|
||||
click.echo('no subcommand, use default')
|
||||
click.echo("no subcommand, use default")
|
||||
ctx.invoke(sync)
|
||||
else:
|
||||
click.echo('invoke subcommand')
|
||||
click.echo("invoke subcommand")
|
||||
|
||||
@cli.command()
|
||||
def sync():
|
||||
click.echo('in subcommand')
|
||||
click.echo("in subcommand")
|
||||
|
||||
result = runner.invoke(cli, ['sync'])
|
||||
result = runner.invoke(cli, ["sync"])
|
||||
assert not result.exception
|
||||
assert result.output == 'invoke subcommand\nin subcommand\n'
|
||||
assert result.output == "invoke subcommand\nin subcommand\n"
|
||||
|
||||
result = runner.invoke(cli)
|
||||
assert not result.exception
|
||||
assert result.output == 'no subcommand, use default\nin subcommand\n'
|
||||
assert result.output == "no subcommand, use default\nin subcommand\n"
|
||||
|
||||
|
||||
def test_unprocessed_options(runner):
|
||||
@click.command(context_settings=dict(
|
||||
ignore_unknown_options=True
|
||||
))
|
||||
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
|
||||
@click.option('--verbose', '-v', count=True)
|
||||
@click.command(context_settings=dict(ignore_unknown_options=True))
|
||||
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
||||
@click.option("--verbose", "-v", count=True)
|
||||
def cli(verbose, args):
|
||||
click.echo('Verbosity: %s' % verbose)
|
||||
click.echo('Args: %s' % '|'.join(args))
|
||||
click.echo("Verbosity: {}".format(verbose))
|
||||
click.echo("Args: {}".format("|".join(args)))
|
||||
|
||||
result = runner.invoke(cli, ['-foo', '-vvvvx', '--muhaha', 'x', 'y', '-x'])
|
||||
result = runner.invoke(cli, ["-foo", "-vvvvx", "--muhaha", "x", "y", "-x"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Verbosity: 4',
|
||||
'Args: -foo|-x|--muhaha|x|y|-x',
|
||||
"Verbosity: 4",
|
||||
"Args: -foo|-x|--muhaha|x|y|-x",
|
||||
]
|
||||
|
||||
|
||||
|
@ -263,21 +281,21 @@ def test_deprecated_in_help_messages(runner):
|
|||
"""CLI HELP"""
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd_with_help, ['--help'])
|
||||
assert '(DEPRECATED)' in result.output
|
||||
result = runner.invoke(cmd_with_help, ["--help"])
|
||||
assert "(DEPRECATED)" in result.output
|
||||
|
||||
@click.command(deprecated=True)
|
||||
def cmd_without_help():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd_without_help, ['--help'])
|
||||
assert '(DEPRECATED)' in result.output
|
||||
result = runner.invoke(cmd_without_help, ["--help"])
|
||||
assert "(DEPRECATED)" in result.output
|
||||
|
||||
|
||||
def test_deprecated_in_invocation(runner):
|
||||
@click.command(deprecated=True)
|
||||
def deprecated_cmd():
|
||||
debug()
|
||||
pass
|
||||
|
||||
result = runner.invoke(deprecated_cmd)
|
||||
assert 'DeprecationWarning:' in result.output
|
||||
assert "DeprecationWarning:" in result.output
|
||||
|
|
|
@ -1,25 +1,47 @@
|
|||
import click
|
||||
import pytest
|
||||
|
||||
import click
|
||||
from click._compat import should_strip_ansi
|
||||
from click._compat import WIN
|
||||
|
||||
if click.__version__ >= '3.0':
|
||||
def test_legacy_callbacks(runner):
|
||||
def legacy_callback(ctx, value):
|
||||
return value.upper()
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', callback=legacy_callback)
|
||||
def cli(foo):
|
||||
click.echo(foo)
|
||||
def test_legacy_callbacks(runner):
|
||||
def legacy_callback(ctx, value):
|
||||
return value.upper()
|
||||
|
||||
with pytest.warns(Warning, match='Invoked legacy parameter callback'):
|
||||
result = runner.invoke(cli, ['--foo', 'wat'])
|
||||
assert result.exit_code == 0
|
||||
assert 'WAT' in result.output
|
||||
@click.command()
|
||||
@click.option("--foo", callback=legacy_callback)
|
||||
def cli(foo):
|
||||
click.echo(foo)
|
||||
|
||||
with pytest.warns(DeprecationWarning, match="2-arg style"):
|
||||
result = runner.invoke(cli, ["--foo", "wat"])
|
||||
assert result.exit_code == 0
|
||||
assert "WAT" in result.output
|
||||
|
||||
|
||||
def test_bash_func_name():
|
||||
from click._bashcomplete import get_completion_script
|
||||
script = get_completion_script('foo-bar baz_blah', '_COMPLETE_VAR', 'bash').strip()
|
||||
assert script.startswith('_foo_barbaz_blah_completion()')
|
||||
assert '_COMPLETE_VAR=complete $1' in script
|
||||
|
||||
script = get_completion_script("foo-bar baz_blah", "_COMPLETE_VAR", "bash").strip()
|
||||
assert script.startswith("_foo_barbaz_blah_completion()")
|
||||
assert "_COMPLETE_VAR=complete $1" in script
|
||||
|
||||
|
||||
def test_zsh_func_name():
|
||||
from click._bashcomplete import get_completion_script
|
||||
|
||||
script = get_completion_script("foo-bar", "_COMPLETE_VAR", "zsh").strip()
|
||||
assert script.startswith("#compdef foo-bar")
|
||||
assert "compdef _foo_bar_completion foo-bar;" in script
|
||||
assert "(( ! $+commands[foo-bar] )) && return 1" in script
|
||||
|
||||
|
||||
@pytest.mark.xfail(WIN, reason="Jupyter not tested/supported on Windows")
|
||||
def test_is_jupyter_kernel_output():
|
||||
class JupyterKernelFakeStream(object):
|
||||
pass
|
||||
|
||||
# implementation detail, aka cheapskate test
|
||||
JupyterKernelFakeStream.__module__ = "ipykernel.faked"
|
||||
assert not should_strip_ansi(stream=JupyterKernelFakeStream())
|
||||
|
|
|
@ -5,7 +5,7 @@ import click
|
|||
def test_ensure_context_objects(runner):
|
||||
class Foo(object):
|
||||
def __init__(self):
|
||||
self.title = 'default'
|
||||
self.title = "default"
|
||||
|
||||
pass_foo = click.make_pass_decorator(Foo, ensure=True)
|
||||
|
||||
|
@ -19,15 +19,15 @@ def test_ensure_context_objects(runner):
|
|||
def test(foo):
|
||||
click.echo(foo.title)
|
||||
|
||||
result = runner.invoke(cli, ['test'])
|
||||
result = runner.invoke(cli, ["test"])
|
||||
assert not result.exception
|
||||
assert result.output == 'default\n'
|
||||
assert result.output == "default\n"
|
||||
|
||||
|
||||
def test_get_context_objects(runner):
|
||||
class Foo(object):
|
||||
def __init__(self):
|
||||
self.title = 'default'
|
||||
self.title = "default"
|
||||
|
||||
pass_foo = click.make_pass_decorator(Foo, ensure=True)
|
||||
|
||||
|
@ -35,22 +35,22 @@ def test_get_context_objects(runner):
|
|||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.obj = Foo()
|
||||
ctx.obj.title = 'test'
|
||||
ctx.obj.title = "test"
|
||||
|
||||
@cli.command()
|
||||
@pass_foo
|
||||
def test(foo):
|
||||
click.echo(foo.title)
|
||||
|
||||
result = runner.invoke(cli, ['test'])
|
||||
result = runner.invoke(cli, ["test"])
|
||||
assert not result.exception
|
||||
assert result.output == 'test\n'
|
||||
assert result.output == "test\n"
|
||||
|
||||
|
||||
def test_get_context_objects_no_ensuring(runner):
|
||||
class Foo(object):
|
||||
def __init__(self):
|
||||
self.title = 'default'
|
||||
self.title = "default"
|
||||
|
||||
pass_foo = click.make_pass_decorator(Foo)
|
||||
|
||||
|
@ -58,16 +58,16 @@ def test_get_context_objects_no_ensuring(runner):
|
|||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.obj = Foo()
|
||||
ctx.obj.title = 'test'
|
||||
ctx.obj.title = "test"
|
||||
|
||||
@cli.command()
|
||||
@pass_foo
|
||||
def test(foo):
|
||||
click.echo(foo.title)
|
||||
|
||||
result = runner.invoke(cli, ['test'])
|
||||
result = runner.invoke(cli, ["test"])
|
||||
assert not result.exception
|
||||
assert result.output == 'test\n'
|
||||
assert result.output == "test\n"
|
||||
|
||||
|
||||
def test_get_context_objects_missing(runner):
|
||||
|
@ -86,11 +86,13 @@ def test_get_context_objects_missing(runner):
|
|||
def test(foo):
|
||||
click.echo(foo.title)
|
||||
|
||||
result = runner.invoke(cli, ['test'])
|
||||
result = runner.invoke(cli, ["test"])
|
||||
assert result.exception is not None
|
||||
assert isinstance(result.exception, RuntimeError)
|
||||
assert "Managed to invoke callback without a context object " \
|
||||
"of type 'Foo' existing" in str(result.exception)
|
||||
assert (
|
||||
"Managed to invoke callback without a context object of type"
|
||||
" 'Foo' existing" in str(result.exception)
|
||||
)
|
||||
|
||||
|
||||
def test_multi_enter(runner):
|
||||
|
@ -101,6 +103,7 @@ def test_multi_enter(runner):
|
|||
def cli(ctx):
|
||||
def callback():
|
||||
called.append(True)
|
||||
|
||||
ctx.call_on_close(callback)
|
||||
|
||||
with ctx:
|
||||
|
@ -117,8 +120,8 @@ def test_global_context_object(runner):
|
|||
@click.pass_context
|
||||
def cli(ctx):
|
||||
assert click.get_current_context() is ctx
|
||||
ctx.obj = 'FOOBAR'
|
||||
assert click.get_current_context().obj == 'FOOBAR'
|
||||
ctx.obj = "FOOBAR"
|
||||
assert click.get_current_context().obj == "FOOBAR"
|
||||
|
||||
assert click.get_current_context(silent=True) is None
|
||||
runner.invoke(cli, [], catch_exceptions=False)
|
||||
|
@ -126,20 +129,20 @@ def test_global_context_object(runner):
|
|||
|
||||
|
||||
def test_context_meta(runner):
|
||||
LANG_KEY = __name__ + '.lang'
|
||||
LANG_KEY = "{}.lang".format(__name__)
|
||||
|
||||
def set_language(value):
|
||||
click.get_current_context().meta[LANG_KEY] = value
|
||||
|
||||
def get_language():
|
||||
return click.get_current_context().meta.get(LANG_KEY, 'en_US')
|
||||
return click.get_current_context().meta.get(LANG_KEY, "en_US")
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
assert get_language() == 'en_US'
|
||||
set_language('de_DE')
|
||||
assert get_language() == 'de_DE'
|
||||
assert get_language() == "en_US"
|
||||
set_language("de_DE")
|
||||
assert get_language() == "de_DE"
|
||||
|
||||
runner.invoke(cli, [], catch_exceptions=False)
|
||||
|
||||
|
@ -174,16 +177,16 @@ def test_pass_obj(runner):
|
|||
@click.group()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.obj = 'test'
|
||||
ctx.obj = "test"
|
||||
|
||||
@cli.command()
|
||||
@click.pass_obj
|
||||
def test(obj):
|
||||
click.echo(obj)
|
||||
|
||||
result = runner.invoke(cli, ['test'])
|
||||
result = runner.invoke(cli, ["test"])
|
||||
assert not result.exception
|
||||
assert result.output == 'test\n'
|
||||
assert result.output == "test\n"
|
||||
|
||||
|
||||
def test_close_before_pop(runner):
|
||||
|
@ -192,17 +195,18 @@ def test_close_before_pop(runner):
|
|||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.obj = 'test'
|
||||
ctx.obj = "test"
|
||||
|
||||
@ctx.call_on_close
|
||||
def foo():
|
||||
assert click.get_current_context().obj == 'test'
|
||||
assert click.get_current_context().obj == "test"
|
||||
called.append(True)
|
||||
click.echo('aha!')
|
||||
|
||||
click.echo("aha!")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == 'aha!\n'
|
||||
assert result.output == "aha!\n"
|
||||
assert called == [True]
|
||||
|
||||
|
||||
|
@ -211,8 +215,9 @@ def test_make_pass_decorator_args(runner):
|
|||
Test to check that make_pass_decorator doesn't consume arguments based on
|
||||
invocation order.
|
||||
"""
|
||||
|
||||
class Foo(object):
|
||||
title = 'foocmd'
|
||||
title = "foocmd"
|
||||
|
||||
pass_foo = click.make_pass_decorator(Foo)
|
||||
|
||||
|
@ -233,13 +238,13 @@ def test_make_pass_decorator_args(runner):
|
|||
def test2(ctx, foo):
|
||||
click.echo(foo.title)
|
||||
|
||||
result = runner.invoke(cli, ['test1'])
|
||||
result = runner.invoke(cli, ["test1"])
|
||||
assert not result.exception
|
||||
assert result.output == 'foocmd\n'
|
||||
assert result.output == "foocmd\n"
|
||||
|
||||
result = runner.invoke(cli, ['test2'])
|
||||
result = runner.invoke(cli, ["test2"])
|
||||
assert not result.exception
|
||||
assert result.output == 'foocmd\n'
|
||||
assert result.output == "foocmd\n"
|
||||
|
||||
|
||||
def test_exit_not_standalone():
|
||||
|
@ -248,11 +253,11 @@ def test_exit_not_standalone():
|
|||
def cli(ctx):
|
||||
ctx.exit(1)
|
||||
|
||||
assert cli.main([], 'test_exit_not_standalone', standalone_mode=False) == 1
|
||||
assert cli.main([], "test_exit_not_standalone", standalone_mode=False) == 1
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.exit(0)
|
||||
|
||||
assert cli.main([], 'test_exit_not_standalone', standalone_mode=False) == 0
|
||||
assert cli.main([], "test_exit_not_standalone", standalone_mode=False) == 0
|
||||
|
|
|
@ -3,20 +3,19 @@ import click
|
|||
|
||||
def test_basic_defaults(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', default=42, type=click.FLOAT)
|
||||
@click.option("--foo", default=42, type=click.FLOAT)
|
||||
def cli(foo):
|
||||
assert type(foo) is float
|
||||
click.echo('FOO:[%s]' % foo)
|
||||
click.echo("FOO:[{}]".format(foo))
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert 'FOO:[42.0]' in result.output
|
||||
assert "FOO:[42.0]" in result.output
|
||||
|
||||
|
||||
def test_multiple_defaults(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', default=[23, 42], type=click.FLOAT,
|
||||
multiple=True)
|
||||
@click.option("--foo", default=[23, 42], type=click.FLOAT, multiple=True)
|
||||
def cli(foo):
|
||||
for item in foo:
|
||||
assert type(item) is float
|
||||
|
@ -24,23 +23,18 @@ def test_multiple_defaults(runner):
|
|||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'23.0',
|
||||
'42.0',
|
||||
]
|
||||
assert result.output.splitlines() == ["23.0", "42.0"]
|
||||
|
||||
|
||||
def test_nargs_plus_multiple(runner):
|
||||
@click.command()
|
||||
@click.option('--arg', default=((1, 2), (3, 4)),
|
||||
nargs=2, multiple=True, type=click.INT)
|
||||
@click.option(
|
||||
"--arg", default=((1, 2), (3, 4)), nargs=2, multiple=True, type=click.INT
|
||||
)
|
||||
def cli(arg):
|
||||
for item in arg:
|
||||
click.echo('<%d|%d>' % item)
|
||||
click.echo("<{0[0]:d}|{0[1]:d}>".format(item))
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'<1|2>',
|
||||
'<3|4>',
|
||||
]
|
||||
assert result.output.splitlines() == ["<1|2>", "<3|4>"]
|
||||
|
|
|
@ -25,28 +25,28 @@ def test_basic_functionality(runner):
|
|||
that will be rewrapped again.
|
||||
"""
|
||||
|
||||
result = runner.invoke(cli, ['--help'], terminal_width=60)
|
||||
result = runner.invoke(cli, ["--help"], terminal_width=60)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cli [OPTIONS]',
|
||||
'',
|
||||
' First paragraph.',
|
||||
'',
|
||||
' This is a very long second paragraph and not correctly',
|
||||
' wrapped but it will be rewrapped.',
|
||||
'',
|
||||
' This is',
|
||||
' a paragraph',
|
||||
' without rewrapping.',
|
||||
'',
|
||||
' 1',
|
||||
' 2',
|
||||
' 3',
|
||||
'',
|
||||
' And this is a paragraph that will be rewrapped again.',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.',
|
||||
"Usage: cli [OPTIONS]",
|
||||
"",
|
||||
" First paragraph.",
|
||||
"",
|
||||
" This is a very long second paragraph and not correctly",
|
||||
" wrapped but it will be rewrapped.",
|
||||
"",
|
||||
" This is",
|
||||
" a paragraph",
|
||||
" without rewrapping.",
|
||||
"",
|
||||
" 1",
|
||||
" 2",
|
||||
" 3",
|
||||
"",
|
||||
" And this is a paragraph that will be rewrapped again.",
|
||||
"",
|
||||
"Options:",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
|
@ -62,30 +62,29 @@ def test_wrapping_long_options_strings(runner):
|
|||
"""
|
||||
|
||||
@a_very_long.command()
|
||||
@click.argument('first')
|
||||
@click.argument('second')
|
||||
@click.argument('third')
|
||||
@click.argument('fourth')
|
||||
@click.argument('fifth')
|
||||
@click.argument('sixth')
|
||||
@click.argument("first")
|
||||
@click.argument("second")
|
||||
@click.argument("third")
|
||||
@click.argument("fourth")
|
||||
@click.argument("fifth")
|
||||
@click.argument("sixth")
|
||||
def command():
|
||||
"""A command.
|
||||
"""
|
||||
|
||||
# 54 is chosen as a length where the second line is one character
|
||||
# longer than the maximum length.
|
||||
result = runner.invoke(cli, ['a-very-long', 'command', '--help'],
|
||||
terminal_width=54)
|
||||
result = runner.invoke(cli, ["a-very-long", "command", "--help"], terminal_width=54)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cli a-very-long command [OPTIONS] FIRST SECOND',
|
||||
' THIRD FOURTH FIFTH',
|
||||
' SIXTH',
|
||||
'',
|
||||
' A command.',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.',
|
||||
"Usage: cli a-very-long command [OPTIONS] FIRST SECOND",
|
||||
" THIRD FOURTH FIFTH",
|
||||
" SIXTH",
|
||||
"",
|
||||
" A command.",
|
||||
"",
|
||||
"Options:",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
|
@ -101,28 +100,29 @@ def test_wrapping_long_command_name(runner):
|
|||
"""
|
||||
|
||||
@a_very_very_very_long.command()
|
||||
@click.argument('first')
|
||||
@click.argument('second')
|
||||
@click.argument('third')
|
||||
@click.argument('fourth')
|
||||
@click.argument('fifth')
|
||||
@click.argument('sixth')
|
||||
@click.argument("first")
|
||||
@click.argument("second")
|
||||
@click.argument("third")
|
||||
@click.argument("fourth")
|
||||
@click.argument("fifth")
|
||||
@click.argument("sixth")
|
||||
def command():
|
||||
"""A command.
|
||||
"""
|
||||
|
||||
result = runner.invoke(cli, ['a-very-very-very-long', 'command', '--help'],
|
||||
terminal_width=54)
|
||||
result = runner.invoke(
|
||||
cli, ["a-very-very-very-long", "command", "--help"], terminal_width=54
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cli a-very-very-very-long command ',
|
||||
' [OPTIONS] FIRST SECOND THIRD FOURTH FIFTH',
|
||||
' SIXTH',
|
||||
'',
|
||||
' A command.',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.',
|
||||
"Usage: cli a-very-very-very-long command ",
|
||||
" [OPTIONS] FIRST SECOND THIRD FOURTH FIFTH",
|
||||
" SIXTH",
|
||||
"",
|
||||
" A command.",
|
||||
"",
|
||||
"Options:",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
|
@ -133,33 +133,33 @@ def test_formatting_empty_help_lines(runner):
|
|||
|
||||
"""
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cli [OPTIONS]',
|
||||
'',
|
||||
' Top level command',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.',
|
||||
"Usage: cli [OPTIONS]",
|
||||
"",
|
||||
" Top level command",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"Options:",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_usage_error(runner):
|
||||
@click.command()
|
||||
@click.argument('arg')
|
||||
@click.argument("arg")
|
||||
def cmd(arg):
|
||||
click.echo('arg:' + arg)
|
||||
click.echo("arg:{}".format(arg))
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.exit_code == 2
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cmd [OPTIONS] ARG',
|
||||
'Try "cmd --help" for help.',
|
||||
'',
|
||||
'Error: Missing argument "ARG".'
|
||||
"Usage: cmd [OPTIONS] ARG",
|
||||
"Try 'cmd --help' for help.",
|
||||
"",
|
||||
"Error: Missing argument 'ARG'.",
|
||||
]
|
||||
|
||||
|
||||
|
@ -168,34 +168,35 @@ def test_formatting_usage_error_metavar_missing_arg(runner):
|
|||
:author: @r-m-n
|
||||
Including attribution to #612
|
||||
"""
|
||||
|
||||
@click.command()
|
||||
@click.argument('arg', metavar='metavar')
|
||||
@click.argument("arg", metavar="metavar")
|
||||
def cmd(arg):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.exit_code == 2
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cmd [OPTIONS] metavar',
|
||||
'Try "cmd --help" for help.',
|
||||
'',
|
||||
'Error: Missing argument "metavar".'
|
||||
"Usage: cmd [OPTIONS] metavar",
|
||||
"Try 'cmd --help' for help.",
|
||||
"",
|
||||
"Error: Missing argument 'metavar'.",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_usage_error_metavar_bad_arg(runner):
|
||||
@click.command()
|
||||
@click.argument('arg', type=click.INT, metavar='metavar')
|
||||
@click.argument("arg", type=click.INT, metavar="metavar")
|
||||
def cmd(arg):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, ['3.14'])
|
||||
result = runner.invoke(cmd, ["3.14"])
|
||||
assert result.exit_code == 2
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cmd [OPTIONS] metavar',
|
||||
'Try "cmd --help" for help.',
|
||||
'',
|
||||
'Error: Invalid value for "metavar": 3.14 is not a valid integer'
|
||||
"Usage: cmd [OPTIONS] metavar",
|
||||
"Try 'cmd --help' for help.",
|
||||
"",
|
||||
"Error: Invalid value for 'metavar': 3.14 is not a valid integer",
|
||||
]
|
||||
|
||||
|
||||
|
@ -205,50 +206,51 @@ def test_formatting_usage_error_nested(runner):
|
|||
pass
|
||||
|
||||
@cmd.command()
|
||||
@click.argument('bar')
|
||||
@click.argument("bar")
|
||||
def foo(bar):
|
||||
click.echo('foo:' + bar)
|
||||
click.echo("foo:{}".format(bar))
|
||||
|
||||
result = runner.invoke(cmd, ['foo'])
|
||||
result = runner.invoke(cmd, ["foo"])
|
||||
assert result.exit_code == 2
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cmd foo [OPTIONS] BAR',
|
||||
'Try "cmd foo --help" for help.',
|
||||
'',
|
||||
'Error: Missing argument "BAR".'
|
||||
"Usage: cmd foo [OPTIONS] BAR",
|
||||
"Try 'cmd foo --help' for help.",
|
||||
"",
|
||||
"Error: Missing argument 'BAR'.",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_usage_error_no_help(runner):
|
||||
@click.command(add_help_option=False)
|
||||
@click.argument('arg')
|
||||
@click.argument("arg")
|
||||
def cmd(arg):
|
||||
click.echo('arg:' + arg)
|
||||
click.echo("arg:{}".format(arg))
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.exit_code == 2
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cmd [OPTIONS] ARG',
|
||||
'',
|
||||
'Error: Missing argument "ARG".'
|
||||
"Usage: cmd [OPTIONS] ARG",
|
||||
"",
|
||||
"Error: Missing argument 'ARG'.",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_usage_custom_help(runner):
|
||||
@click.command(context_settings=dict(help_option_names=['--man']))
|
||||
@click.argument('arg')
|
||||
@click.command(context_settings=dict(help_option_names=["--man"]))
|
||||
@click.argument("arg")
|
||||
def cmd(arg):
|
||||
click.echo('arg:' + arg)
|
||||
click.echo("arg:{}".format(arg))
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.exit_code == 2
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cmd [OPTIONS] ARG',
|
||||
'Try "cmd --man" for help.',
|
||||
'',
|
||||
'Error: Missing argument "ARG".'
|
||||
"Usage: cmd [OPTIONS] ARG",
|
||||
"Try 'cmd --man' for help.",
|
||||
"",
|
||||
"Error: Missing argument 'ARG'.",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_custom_type_metavar(runner):
|
||||
class MyType(click.ParamType):
|
||||
def get_metavar(self, param):
|
||||
|
@ -260,13 +262,13 @@ def test_formatting_custom_type_metavar(runner):
|
|||
def cmd(param):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, '--help')
|
||||
result = runner.invoke(cmd, "--help")
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: foo [OPTIONS] MY_TYPE',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.'
|
||||
"Usage: foo [OPTIONS] MY_TYPE",
|
||||
"",
|
||||
"Options:",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
|
@ -284,16 +286,68 @@ def test_truncating_docstring(runner):
|
|||
:param click.core.Context ctx: Click context.
|
||||
"""
|
||||
|
||||
result = runner.invoke(cli, ['--help'], terminal_width=60)
|
||||
result = runner.invoke(cli, ["--help"], terminal_width=60)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cli [OPTIONS]',
|
||||
'',
|
||||
' First paragraph.',
|
||||
'',
|
||||
' This is a very long second paragraph and not correctly',
|
||||
' wrapped but it will be rewrapped.',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.',
|
||||
"Usage: cli [OPTIONS]",
|
||||
"",
|
||||
" First paragraph.",
|
||||
"",
|
||||
" This is a very long second paragraph and not correctly",
|
||||
" wrapped but it will be rewrapped.",
|
||||
"",
|
||||
"Options:",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
def test_global_show_default(runner):
|
||||
@click.command(context_settings=dict(show_default=True))
|
||||
@click.option("-f", "in_file", default="out.txt", help="Output file name")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, ["--help"],)
|
||||
assert result.output.splitlines() == [
|
||||
"Usage: cli [OPTIONS]",
|
||||
"",
|
||||
"Options:",
|
||||
" -f TEXT Output file name [default: out.txt]",
|
||||
" --help Show this message and exit. [default: False]",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_usage_multiline_option_padding(runner):
|
||||
@click.command("foo")
|
||||
@click.option("--bar", help="This help message will be padded if it wraps.")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, "--help", terminal_width=45)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
"Usage: foo [OPTIONS]",
|
||||
"",
|
||||
"Options:",
|
||||
" --bar TEXT This help message will be",
|
||||
" padded if it wraps.",
|
||||
"",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_usage_no_option_padding(runner):
|
||||
@click.command("foo")
|
||||
@click.option("--bar", help="This help message will be padded if it wraps.")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, "--help", terminal_width=80)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
"Usage: foo [OPTIONS]",
|
||||
"",
|
||||
"Options:",
|
||||
" --bar TEXT This help message will be padded if it wraps.",
|
||||
" --help Show this message and exit.",
|
||||
]
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from click._compat import WIN
|
||||
|
||||
|
||||
IMPORT_TEST = b'''\
|
||||
IMPORT_TEST = b"""\
|
||||
try:
|
||||
import __builtin__ as builtins
|
||||
except ImportError:
|
||||
|
@ -27,29 +27,45 @@ import click
|
|||
rv = list(found_imports)
|
||||
import json
|
||||
click.echo(json.dumps(rv))
|
||||
'''
|
||||
"""
|
||||
|
||||
ALLOWED_IMPORTS = set([
|
||||
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
||||
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
|
||||
'threading', 'colorama', 'errno', 'fcntl', 'datetime'
|
||||
])
|
||||
ALLOWED_IMPORTS = {
|
||||
"weakref",
|
||||
"os",
|
||||
"struct",
|
||||
"collections",
|
||||
"sys",
|
||||
"contextlib",
|
||||
"functools",
|
||||
"stat",
|
||||
"re",
|
||||
"codecs",
|
||||
"inspect",
|
||||
"itertools",
|
||||
"io",
|
||||
"threading",
|
||||
"colorama",
|
||||
"errno",
|
||||
"fcntl",
|
||||
"datetime",
|
||||
"pipes",
|
||||
}
|
||||
|
||||
if WIN:
|
||||
ALLOWED_IMPORTS.update(['ctypes', 'ctypes.wintypes', 'msvcrt', 'time',
|
||||
'zlib'])
|
||||
ALLOWED_IMPORTS.update(["ctypes", "ctypes.wintypes", "msvcrt", "time", "zlib"])
|
||||
|
||||
|
||||
def test_light_imports():
|
||||
c = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
c = subprocess.Popen(
|
||||
[sys.executable, "-"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
||||
)
|
||||
rv = c.communicate(IMPORT_TEST)[0]
|
||||
|
||||
if sys.version_info[0] != 2:
|
||||
rv = rv.decode('utf-8')
|
||||
rv = rv.decode("utf-8")
|
||||
imported = json.loads(rv)
|
||||
|
||||
for module in imported:
|
||||
if module == 'click' or module.startswith('click.'):
|
||||
if module == "click" or module.startswith("click."):
|
||||
continue
|
||||
assert module in ALLOWED_IMPORTS
|
||||
|
|
|
@ -6,24 +6,24 @@ CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.lower())
|
|||
|
||||
def test_option_normalization(runner):
|
||||
@click.command(context_settings=CONTEXT_SETTINGS)
|
||||
@click.option('--foo')
|
||||
@click.option('-x')
|
||||
@click.option("--foo")
|
||||
@click.option("-x")
|
||||
def cli(foo, x):
|
||||
click.echo(foo)
|
||||
click.echo(x)
|
||||
|
||||
result = runner.invoke(cli, ['--FOO', '42', '-X', 23])
|
||||
assert result.output == '42\n23\n'
|
||||
result = runner.invoke(cli, ["--FOO", "42", "-X", 23])
|
||||
assert result.output == "42\n23\n"
|
||||
|
||||
|
||||
def test_choice_normalization(runner):
|
||||
@click.command(context_settings=CONTEXT_SETTINGS)
|
||||
@click.option('--choice', type=click.Choice(['Foo', 'Bar']))
|
||||
@click.option("--choice", type=click.Choice(["Foo", "Bar"]))
|
||||
def cli(choice):
|
||||
click.echo('Foo')
|
||||
click.echo(choice)
|
||||
|
||||
result = runner.invoke(cli, ['--CHOICE', 'FOO'])
|
||||
assert result.output == 'Foo\n'
|
||||
result = runner.invoke(cli, ["--CHOICE", "FOO"])
|
||||
assert result.output == "Foo\n"
|
||||
|
||||
|
||||
def test_command_normalization(runner):
|
||||
|
@ -33,7 +33,7 @@ def test_command_normalization(runner):
|
|||
|
||||
@cli.command()
|
||||
def foo():
|
||||
click.echo('here!')
|
||||
click.echo("here!")
|
||||
|
||||
result = runner.invoke(cli, ['FOO'])
|
||||
assert result.output == 'here!\n'
|
||||
result = runner.invoke(cli, ["FOO"])
|
||||
assert result.output == "here!\n"
|
||||
|
|
|
@ -1,93 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import os
|
||||
import click
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
import click
|
||||
from click._compat import text_type
|
||||
|
||||
|
||||
def test_prefixes(runner):
|
||||
@click.command()
|
||||
@click.option('++foo', is_flag=True, help='das foo')
|
||||
@click.option('--bar', is_flag=True, help='das bar')
|
||||
@click.option("++foo", is_flag=True, help="das foo")
|
||||
@click.option("--bar", is_flag=True, help="das bar")
|
||||
def cli(foo, bar):
|
||||
click.echo('foo=%s bar=%s' % (foo, bar))
|
||||
click.echo("foo={} bar={}".format(foo, bar))
|
||||
|
||||
result = runner.invoke(cli, ['++foo', '--bar'])
|
||||
result = runner.invoke(cli, ["++foo", "--bar"])
|
||||
assert not result.exception
|
||||
assert result.output == 'foo=True bar=True\n'
|
||||
assert result.output == "foo=True bar=True\n"
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert re.search(r'\+\+foo\s+das foo', result.output) is not None
|
||||
assert re.search(r'--bar\s+das bar', result.output) is not None
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert re.search(r"\+\+foo\s+das foo", result.output) is not None
|
||||
assert re.search(r"--bar\s+das bar", result.output) is not None
|
||||
|
||||
|
||||
def test_invalid_option(runner):
|
||||
try:
|
||||
with pytest.raises(TypeError, match="name was passed"):
|
||||
|
||||
@click.command()
|
||||
@click.option('foo')
|
||||
@click.option("foo")
|
||||
def cli(foo):
|
||||
pass
|
||||
except TypeError as e:
|
||||
assert 'No options defined but a name was passed (foo).' \
|
||||
in str(e)
|
||||
else:
|
||||
assert False, 'Expected a type error because of an invalid option.'
|
||||
|
||||
|
||||
def test_invalid_nargs(runner):
|
||||
try:
|
||||
with pytest.raises(TypeError, match="nargs < 0"):
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', nargs=-1)
|
||||
@click.option("--foo", nargs=-1)
|
||||
def cli(foo):
|
||||
pass
|
||||
except TypeError as e:
|
||||
assert 'Options cannot have nargs < 0' in str(e)
|
||||
else:
|
||||
assert False, 'Expected a type error because of an invalid option.'
|
||||
|
||||
|
||||
def test_nargs_tup_composite_mult(runner):
|
||||
@click.command()
|
||||
@click.option('--item', type=(str, int), multiple=True)
|
||||
@click.option("--item", type=(str, int), multiple=True)
|
||||
def copy(item):
|
||||
for item in item:
|
||||
click.echo('name=%s id=%d' % item)
|
||||
click.echo("name={0[0]} id={0[1]:d}".format(item))
|
||||
|
||||
result = runner.invoke(copy, ['--item', 'peter', '1', '--item', 'max', '2'])
|
||||
result = runner.invoke(copy, ["--item", "peter", "1", "--item", "max", "2"])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'name=peter id=1',
|
||||
'name=max id=2',
|
||||
]
|
||||
assert result.output.splitlines() == ["name=peter id=1", "name=max id=2"]
|
||||
|
||||
|
||||
def test_counting(runner):
|
||||
@click.command()
|
||||
@click.option('-v', count=True, help='Verbosity',
|
||||
type=click.IntRange(0, 3))
|
||||
@click.option("-v", count=True, help="Verbosity", type=click.IntRange(0, 3))
|
||||
def cli(v):
|
||||
click.echo('verbosity=%d' % v)
|
||||
click.echo("verbosity={:d}".format(v))
|
||||
|
||||
result = runner.invoke(cli, ['-vvv'])
|
||||
result = runner.invoke(cli, ["-vvv"])
|
||||
assert not result.exception
|
||||
assert result.output == 'verbosity=3\n'
|
||||
assert result.output == "verbosity=3\n"
|
||||
|
||||
result = runner.invoke(cli, ['-vvvv'])
|
||||
result = runner.invoke(cli, ["-vvvv"])
|
||||
assert result.exception
|
||||
assert 'Invalid value for "-v": 4 is not in the valid range of 0 to 3.' \
|
||||
assert (
|
||||
"Invalid value for '-v': 4 is not in the valid range of 0 to 3."
|
||||
in result.output
|
||||
)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == 'verbosity=0\n'
|
||||
assert result.output == "verbosity=0\n"
|
||||
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert re.search(r'-v\s+Verbosity', result.output) is not None
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert re.search(r"-v\s+Verbosity", result.output) is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('unknown_flag', ['--foo', '-f'])
|
||||
@pytest.mark.parametrize("unknown_flag", ["--foo", "-f"])
|
||||
def test_unknown_options(runner, unknown_flag):
|
||||
@click.command()
|
||||
def cli():
|
||||
|
@ -95,74 +87,88 @@ def test_unknown_options(runner, unknown_flag):
|
|||
|
||||
result = runner.invoke(cli, [unknown_flag])
|
||||
assert result.exception
|
||||
assert 'no such option: {0}'.format(unknown_flag) in result.output
|
||||
assert "no such option: {}".format(unknown_flag) in result.output
|
||||
|
||||
|
||||
def test_multiple_required(runner):
|
||||
@click.command()
|
||||
@click.option('-m', '--message', multiple=True, required=True)
|
||||
@click.option("-m", "--message", multiple=True, required=True)
|
||||
def cli(message):
|
||||
click.echo('\n'.join(message))
|
||||
click.echo("\n".join(message))
|
||||
|
||||
result = runner.invoke(cli, ['-m', 'foo', '-mbar'])
|
||||
result = runner.invoke(cli, ["-m", "foo", "-mbar"])
|
||||
assert not result.exception
|
||||
assert result.output == 'foo\nbar\n'
|
||||
assert result.output == "foo\nbar\n"
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exception
|
||||
assert 'Error: Missing option "-m" / "--message".' in result.output
|
||||
assert "Error: Missing option '-m' / '--message'." in result.output
|
||||
|
||||
|
||||
def test_empty_envvar(runner):
|
||||
@click.command()
|
||||
@click.option("--mypath", type=click.Path(exists=True), envvar="MYPATH")
|
||||
def cli(mypath):
|
||||
click.echo("mypath: {}".format(mypath))
|
||||
|
||||
result = runner.invoke(cli, [], env={"MYPATH": ""})
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "mypath: None\n"
|
||||
|
||||
|
||||
def test_multiple_envvar(runner):
|
||||
@click.command()
|
||||
@click.option('--arg', multiple=True)
|
||||
@click.option("--arg", multiple=True)
|
||||
def cmd(arg):
|
||||
click.echo('|'.join(arg))
|
||||
click.echo("|".join(arg))
|
||||
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix='TEST',
|
||||
env={'TEST_ARG': 'foo bar baz'})
|
||||
result = runner.invoke(
|
||||
cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar baz"}
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output == 'foo|bar|baz\n'
|
||||
assert result.output == "foo|bar|baz\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--arg', multiple=True, envvar='X')
|
||||
@click.option("--arg", multiple=True, envvar="X")
|
||||
def cmd(arg):
|
||||
click.echo('|'.join(arg))
|
||||
click.echo("|".join(arg))
|
||||
|
||||
result = runner.invoke(cmd, [], env={'X': 'foo bar baz'})
|
||||
result = runner.invoke(cmd, [], env={"X": "foo bar baz"})
|
||||
assert not result.exception
|
||||
assert result.output == 'foo|bar|baz\n'
|
||||
assert result.output == "foo|bar|baz\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--arg', multiple=True, type=click.Path())
|
||||
@click.option("--arg", multiple=True, type=click.Path())
|
||||
def cmd(arg):
|
||||
click.echo('|'.join(arg))
|
||||
click.echo("|".join(arg))
|
||||
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix='TEST',
|
||||
env={'TEST_ARG': 'foo%sbar' % os.path.pathsep})
|
||||
result = runner.invoke(
|
||||
cmd,
|
||||
[],
|
||||
auto_envvar_prefix="TEST",
|
||||
env={"TEST_ARG": "foo{}bar".format(os.path.pathsep)},
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output == 'foo|bar\n'
|
||||
assert result.output == "foo|bar\n"
|
||||
|
||||
|
||||
def test_multiple_default_help(runner):
|
||||
@click.command()
|
||||
@click.option('--arg1', multiple=True, default=('foo', 'bar'),
|
||||
show_default=True)
|
||||
@click.option('--arg2', multiple=True, default=(1, 2), type=int,
|
||||
show_default=True)
|
||||
@click.option("--arg1", multiple=True, default=("foo", "bar"), show_default=True)
|
||||
@click.option("--arg2", multiple=True, default=(1, 2), type=int, show_default=True)
|
||||
def cmd(arg, arg2):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert not result.exception
|
||||
assert 'foo, bar' in result.output
|
||||
assert '1, 2' in result.output
|
||||
assert "foo, bar" in result.output
|
||||
assert "1, 2" in result.output
|
||||
|
||||
|
||||
def test_multiple_default_type(runner):
|
||||
@click.command()
|
||||
@click.option('--arg1', multiple=True, default=('foo', 'bar'))
|
||||
@click.option('--arg2', multiple=True, default=(1, 'a'))
|
||||
@click.option("--arg1", multiple=True, default=("foo", "bar"))
|
||||
@click.option("--arg2", multiple=True, default=(1, "a"))
|
||||
def cmd(arg1, arg2):
|
||||
assert all(isinstance(e[0], text_type) for e in arg1)
|
||||
assert all(isinstance(e[1], text_type) for e in arg1)
|
||||
|
@ -170,242 +176,292 @@ def test_multiple_default_type(runner):
|
|||
assert all(isinstance(e[0], int) for e in arg2)
|
||||
assert all(isinstance(e[1], text_type) for e in arg2)
|
||||
|
||||
result = runner.invoke(cmd, '--arg1 a b --arg1 test 1 --arg2 2 '
|
||||
'two --arg2 4 four'.split())
|
||||
result = runner.invoke(
|
||||
cmd, "--arg1 a b --arg1 test 1 --arg2 2 two --arg2 4 four".split()
|
||||
)
|
||||
assert not result.exception
|
||||
|
||||
|
||||
def test_dynamic_default_help_unset(runner):
|
||||
@click.command()
|
||||
@click.option('--username', prompt=True,
|
||||
default=lambda: os.environ.get('USER', ''),
|
||||
show_default=True)
|
||||
@click.option(
|
||||
"--username",
|
||||
prompt=True,
|
||||
default=lambda: os.environ.get("USER", ""),
|
||||
show_default=True,
|
||||
)
|
||||
def cmd(username):
|
||||
print("Hello,", username)
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert '--username' in result.output
|
||||
assert 'lambda' not in result.output
|
||||
assert '(dynamic)' in result.output
|
||||
assert "--username" in result.output
|
||||
assert "lambda" not in result.output
|
||||
assert "(dynamic)" in result.output
|
||||
|
||||
|
||||
def test_dynamic_default_help_text(runner):
|
||||
@click.command()
|
||||
@click.option('--username', prompt=True,
|
||||
default=lambda: os.environ.get('USER', ''),
|
||||
show_default='current user')
|
||||
@click.option(
|
||||
"--username",
|
||||
prompt=True,
|
||||
default=lambda: os.environ.get("USER", ""),
|
||||
show_default="current user",
|
||||
)
|
||||
def cmd(username):
|
||||
print("Hello,", username)
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert '--username' in result.output
|
||||
assert 'lambda' not in result.output
|
||||
assert '(current user)' in result.output
|
||||
assert "--username" in result.output
|
||||
assert "lambda" not in result.output
|
||||
assert "(current user)" in result.output
|
||||
|
||||
|
||||
def test_toupper_envvar_prefix(runner):
|
||||
@click.command()
|
||||
@click.option('--arg')
|
||||
@click.option("--arg")
|
||||
def cmd(arg):
|
||||
click.echo(arg)
|
||||
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix='test',
|
||||
env={'TEST_ARG': 'foo'})
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix="test", env={"TEST_ARG": "foo"})
|
||||
assert not result.exception
|
||||
assert result.output == 'foo\n'
|
||||
assert result.output == "foo\n"
|
||||
|
||||
|
||||
def test_nargs_envvar(runner):
|
||||
@click.command()
|
||||
@click.option('--arg', nargs=2)
|
||||
@click.option("--arg", nargs=2)
|
||||
def cmd(arg):
|
||||
click.echo('|'.join(arg))
|
||||
click.echo("|".join(arg))
|
||||
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix='TEST',
|
||||
env={'TEST_ARG': 'foo bar'})
|
||||
result = runner.invoke(
|
||||
cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar"}
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output == 'foo|bar\n'
|
||||
assert result.output == "foo|bar\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--arg', nargs=2, multiple=True)
|
||||
@click.option("--arg", nargs=2, multiple=True)
|
||||
def cmd(arg):
|
||||
for item in arg:
|
||||
click.echo('|'.join(item))
|
||||
click.echo("|".join(item))
|
||||
|
||||
result = runner.invoke(cmd, [], auto_envvar_prefix='TEST',
|
||||
env={'TEST_ARG': 'x 1 y 2'})
|
||||
result = runner.invoke(
|
||||
cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "x 1 y 2"}
|
||||
)
|
||||
assert not result.exception
|
||||
assert result.output == 'x|1\ny|2\n'
|
||||
assert result.output == "x|1\ny|2\n"
|
||||
|
||||
|
||||
def test_show_envvar(runner):
|
||||
@click.command()
|
||||
@click.option('--arg1', envvar='ARG1',
|
||||
show_envvar=True)
|
||||
@click.option("--arg1", envvar="ARG1", show_envvar=True)
|
||||
def cmd(arg):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert not result.exception
|
||||
assert 'ARG1' in result.output
|
||||
assert "ARG1" in result.output
|
||||
|
||||
|
||||
def test_show_envvar_auto_prefix(runner):
|
||||
@click.command()
|
||||
@click.option('--arg1', show_envvar=True)
|
||||
@click.option("--arg1", show_envvar=True)
|
||||
def cmd(arg):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, ['--help'],
|
||||
auto_envvar_prefix='TEST')
|
||||
result = runner.invoke(cmd, ["--help"], auto_envvar_prefix="TEST")
|
||||
assert not result.exception
|
||||
assert 'TEST_ARG1' in result.output
|
||||
assert "TEST_ARG1" in result.output
|
||||
|
||||
|
||||
def test_show_envvar_auto_prefix_dash_in_command(runner):
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option("--baz", show_envvar=True)
|
||||
def foo_bar(baz):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli, ["foo-bar", "--help"], auto_envvar_prefix="TEST")
|
||||
assert not result.exception
|
||||
assert "TEST_FOO_BAR_BAZ" in result.output
|
||||
|
||||
|
||||
def test_custom_validation(runner):
|
||||
def validate_pos_int(ctx, param, value):
|
||||
if value < 0:
|
||||
raise click.BadParameter('Value needs to be positive')
|
||||
raise click.BadParameter("Value needs to be positive")
|
||||
return value
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', callback=validate_pos_int, default=1)
|
||||
@click.option("--foo", callback=validate_pos_int, default=1)
|
||||
def cmd(foo):
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cmd, ['--foo', '-1'])
|
||||
assert 'Invalid value for "--foo": Value needs to be positive' \
|
||||
in result.output
|
||||
result = runner.invoke(cmd, ["--foo", "-1"])
|
||||
assert "Invalid value for '--foo': Value needs to be positive" in result.output
|
||||
|
||||
result = runner.invoke(cmd, ['--foo', '42'])
|
||||
assert result.output == '42\n'
|
||||
result = runner.invoke(cmd, ["--foo", "42"])
|
||||
assert result.output == "42\n"
|
||||
|
||||
|
||||
def test_winstyle_options(runner):
|
||||
@click.command()
|
||||
@click.option('/debug;/no-debug', help='Enables or disables debug mode.')
|
||||
@click.option("/debug;/no-debug", help="Enables or disables debug mode.")
|
||||
def cmd(debug):
|
||||
click.echo(debug)
|
||||
|
||||
result = runner.invoke(cmd, ['/debug'], help_option_names=['/?'])
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cmd, ['/no-debug'], help_option_names=['/?'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cmd, [], help_option_names=['/?'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cmd, ['/?'], help_option_names=['/?'])
|
||||
assert '/debug; /no-debug Enables or disables debug mode.' in result.output
|
||||
assert '/? Show this message and exit.' in result.output
|
||||
result = runner.invoke(cmd, ["/debug"], help_option_names=["/?"])
|
||||
assert result.output == "True\n"
|
||||
result = runner.invoke(cmd, ["/no-debug"], help_option_names=["/?"])
|
||||
assert result.output == "False\n"
|
||||
result = runner.invoke(cmd, [], help_option_names=["/?"])
|
||||
assert result.output == "False\n"
|
||||
result = runner.invoke(cmd, ["/?"], help_option_names=["/?"])
|
||||
assert "/debug; /no-debug Enables or disables debug mode." in result.output
|
||||
assert "/? Show this message and exit." in result.output
|
||||
|
||||
|
||||
def test_legacy_options(runner):
|
||||
@click.command()
|
||||
@click.option('-whatever')
|
||||
@click.option("-whatever")
|
||||
def cmd(whatever):
|
||||
click.echo(whatever)
|
||||
|
||||
result = runner.invoke(cmd, ['-whatever', '42'])
|
||||
assert result.output == '42\n'
|
||||
result = runner.invoke(cmd, ['-whatever=23'])
|
||||
assert result.output == '23\n'
|
||||
result = runner.invoke(cmd, ["-whatever", "42"])
|
||||
assert result.output == "42\n"
|
||||
result = runner.invoke(cmd, ["-whatever=23"])
|
||||
assert result.output == "23\n"
|
||||
|
||||
|
||||
def test_missing_option_string_cast():
|
||||
ctx = click.Context(click.Command(""))
|
||||
|
||||
with pytest.raises(click.MissingParameter) as excinfo:
|
||||
click.Option(["-a"], required=True).full_process_value(ctx, None)
|
||||
|
||||
assert str(excinfo.value) == "missing parameter: a"
|
||||
|
||||
|
||||
def test_missing_choice(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', type=click.Choice(['foo', 'bar']),
|
||||
required=True)
|
||||
@click.option("--foo", type=click.Choice(["foo", "bar"]), required=True)
|
||||
def cmd(foo):
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cmd)
|
||||
assert result.exit_code == 2
|
||||
error, separator, choices = result.output.partition('Choose from')
|
||||
assert 'Error: Missing option "--foo". ' in error
|
||||
assert 'Choose from' in separator
|
||||
assert 'foo' in choices
|
||||
assert 'bar' in choices
|
||||
error, separator, choices = result.output.partition("Choose from")
|
||||
assert "Error: Missing option '--foo'. " in error
|
||||
assert "Choose from" in separator
|
||||
assert "foo" in choices
|
||||
assert "bar" in choices
|
||||
|
||||
|
||||
def test_case_insensitive_choice(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', type=click.Choice(
|
||||
['Orange', 'Apple'], case_sensitive=False))
|
||||
@click.option("--foo", type=click.Choice(["Orange", "Apple"], case_sensitive=False))
|
||||
def cmd(foo):
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cmd, ['--foo', 'apple'])
|
||||
result = runner.invoke(cmd, ["--foo", "apple"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "Apple\n"
|
||||
|
||||
result = runner.invoke(cmd, ['--foo', 'oRANGe'])
|
||||
result = runner.invoke(cmd, ["--foo", "oRANGe"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "Orange\n"
|
||||
|
||||
result = runner.invoke(cmd, ['--foo', 'Apple'])
|
||||
result = runner.invoke(cmd, ["--foo", "Apple"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "Apple\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', type=click.Choice(['Orange', 'Apple']))
|
||||
@click.option("--foo", type=click.Choice(["Orange", "Apple"]))
|
||||
def cmd2(foo):
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cmd2, ['--foo', 'apple'])
|
||||
result = runner.invoke(cmd2, ["--foo", "apple"])
|
||||
assert result.exit_code == 2
|
||||
|
||||
result = runner.invoke(cmd2, ['--foo', 'oRANGe'])
|
||||
result = runner.invoke(cmd2, ["--foo", "oRANGe"])
|
||||
assert result.exit_code == 2
|
||||
|
||||
result = runner.invoke(cmd2, ['--foo', 'Apple'])
|
||||
result = runner.invoke(cmd2, ["--foo", "Apple"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_multiline_help(runner):
|
||||
def test_case_insensitive_choice_returned_exactly(runner):
|
||||
@click.command()
|
||||
@click.option('--foo', help="""
|
||||
hello
|
||||
|
||||
i am
|
||||
|
||||
multiline
|
||||
""")
|
||||
@click.option("--foo", type=click.Choice(["Orange", "Apple"], case_sensitive=False))
|
||||
def cmd(foo):
|
||||
click.echo(foo)
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
result = runner.invoke(cmd, ["--foo", "apple"])
|
||||
assert result.exit_code == 0
|
||||
out = result.output.splitlines()
|
||||
assert ' --foo TEXT hello' in out
|
||||
assert ' i am' in out
|
||||
assert ' multiline' in out
|
||||
assert result.output == "Apple\n"
|
||||
|
||||
|
||||
def test_option_help_preserve_paragraphs(runner):
|
||||
@click.command()
|
||||
@click.option(
|
||||
"-C",
|
||||
"--config",
|
||||
type=click.Path(),
|
||||
help="""Configuration file to use.
|
||||
|
||||
If not given, the environment variable CONFIG_FILE is consulted
|
||||
and used if set. If neither are given, a default configuration
|
||||
file is loaded.""",
|
||||
)
|
||||
def cmd(config):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd, ["--help"],)
|
||||
assert result.exit_code == 0
|
||||
assert (
|
||||
" -C, --config PATH Configuration file to use.\n"
|
||||
"{i}\n"
|
||||
"{i}If not given, the environment variable CONFIG_FILE is\n"
|
||||
"{i}consulted and used if set. If neither are given, a default\n"
|
||||
"{i}configuration file is loaded.".format(i=" " * 21)
|
||||
) in result.output
|
||||
|
||||
|
||||
def test_argument_custom_class(runner):
|
||||
class CustomArgument(click.Argument):
|
||||
def get_default(self, ctx):
|
||||
'''a dumb override of a default value for testing'''
|
||||
return 'I am a default'
|
||||
"""a dumb override of a default value for testing"""
|
||||
return "I am a default"
|
||||
|
||||
@click.command()
|
||||
@click.argument('testarg', cls=CustomArgument, default='you wont see me')
|
||||
@click.argument("testarg", cls=CustomArgument, default="you wont see me")
|
||||
def cmd(testarg):
|
||||
click.echo(testarg)
|
||||
|
||||
result = runner.invoke(cmd)
|
||||
assert 'I am a default' in result.output
|
||||
assert 'you wont see me' not in result.output
|
||||
assert "I am a default" in result.output
|
||||
assert "you wont see me" not in result.output
|
||||
|
||||
|
||||
def test_option_custom_class(runner):
|
||||
class CustomOption(click.Option):
|
||||
def get_help_record(self, ctx):
|
||||
'''a dumb override of a help text for testing'''
|
||||
return ('--help', 'I am a help text')
|
||||
"""a dumb override of a help text for testing"""
|
||||
return ("--help", "I am a help text")
|
||||
|
||||
@click.command()
|
||||
@click.option('--testoption', cls=CustomOption, help='you wont see me')
|
||||
@click.option("--testoption", cls=CustomOption, help="you wont see me")
|
||||
def cmd(testoption):
|
||||
click.echo(testoption)
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
assert 'I am a help text' in result.output
|
||||
assert 'you wont see me' not in result.output
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert "I am a help text" in result.output
|
||||
assert "you wont see me" not in result.output
|
||||
|
||||
|
||||
def test_option_custom_class_reusable(runner):
|
||||
|
@ -413,11 +469,11 @@ def test_option_custom_class_reusable(runner):
|
|||
|
||||
class CustomOption(click.Option):
|
||||
def get_help_record(self, ctx):
|
||||
'''a dumb override of a help text for testing'''
|
||||
return ('--help', 'I am a help text')
|
||||
"""a dumb override of a help text for testing"""
|
||||
return ("--help", "I am a help text")
|
||||
|
||||
# Assign to a variable to re-use the decorator.
|
||||
testoption = click.option('--testoption', cls=CustomOption, help='you wont see me')
|
||||
testoption = click.option("--testoption", cls=CustomOption, help="you wont see me")
|
||||
|
||||
@click.command()
|
||||
@testoption
|
||||
|
@ -432,50 +488,63 @@ def test_option_custom_class_reusable(runner):
|
|||
# Both of the commands should have the --help option now.
|
||||
for cmd in (cmd1, cmd2):
|
||||
|
||||
result = runner.invoke(cmd, ['--help'])
|
||||
assert 'I am a help text' in result.output
|
||||
assert 'you wont see me' not in result.output
|
||||
result = runner.invoke(cmd, ["--help"])
|
||||
assert "I am a help text" in result.output
|
||||
assert "you wont see me" not in result.output
|
||||
|
||||
|
||||
def test_bool_flag_with_type(runner):
|
||||
@click.command()
|
||||
@click.option("--shout/--no-shout", default=False, type=bool)
|
||||
def cmd(shout):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cmd)
|
||||
assert not result.exception
|
||||
|
||||
|
||||
def test_aliases_for_flags(runner):
|
||||
@click.command()
|
||||
@click.option('--warnings/--no-warnings', ' /-W', default=True)
|
||||
@click.option("--warnings/--no-warnings", " /-W", default=True)
|
||||
def cli(warnings):
|
||||
click.echo(warnings)
|
||||
|
||||
result = runner.invoke(cli, ['--warnings'])
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cli, ['--no-warnings'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cli, ['-W'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cli, ["--warnings"])
|
||||
assert result.output == "True\n"
|
||||
result = runner.invoke(cli, ["--no-warnings"])
|
||||
assert result.output == "False\n"
|
||||
result = runner.invoke(cli, ["-W"])
|
||||
assert result.output == "False\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--warnings/--no-warnings', '-w', default=True)
|
||||
@click.option("--warnings/--no-warnings", "-w", default=True)
|
||||
def cli_alt(warnings):
|
||||
click.echo(warnings)
|
||||
|
||||
result = runner.invoke(cli_alt, ['--warnings'])
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cli_alt, ['--no-warnings'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cli_alt, ['-w'])
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cli_alt, ["--warnings"])
|
||||
assert result.output == "True\n"
|
||||
result = runner.invoke(cli_alt, ["--no-warnings"])
|
||||
assert result.output == "False\n"
|
||||
result = runner.invoke(cli_alt, ["-w"])
|
||||
assert result.output == "True\n"
|
||||
|
||||
@pytest.mark.parametrize('option_args,expected', [
|
||||
(['--aggressive', '--all', '-a'], 'aggressive'),
|
||||
(['--first', '--second', '--third', '-a', '-b', '-c'], 'first'),
|
||||
(['--apple', '--banana', '--cantaloupe', '-a', '-b', '-c'], 'apple'),
|
||||
(['--cantaloupe', '--banana', '--apple', '-c', '-b', '-a'], 'cantaloupe'),
|
||||
(['-a', '-b', '-c'], 'a'),
|
||||
(['-c', '-b', '-a'], 'c'),
|
||||
(['-a', '--apple', '-b', '--banana', '-c', '--cantaloupe'], 'apple'),
|
||||
(['-c', '-a', '--cantaloupe', '-b', '--banana', '--apple'], 'cantaloupe'),
|
||||
(['--from', '-f', '_from'], '_from'),
|
||||
(['--return', '-r', '_ret'], '_ret'),
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"option_args,expected",
|
||||
[
|
||||
(["--aggressive", "--all", "-a"], "aggressive"),
|
||||
(["--first", "--second", "--third", "-a", "-b", "-c"], "first"),
|
||||
(["--apple", "--banana", "--cantaloupe", "-a", "-b", "-c"], "apple"),
|
||||
(["--cantaloupe", "--banana", "--apple", "-c", "-b", "-a"], "cantaloupe"),
|
||||
(["-a", "-b", "-c"], "a"),
|
||||
(["-c", "-b", "-a"], "c"),
|
||||
(["-a", "--apple", "-b", "--banana", "-c", "--cantaloupe"], "apple"),
|
||||
(["-c", "-a", "--cantaloupe", "-b", "--banana", "--apple"], "cantaloupe"),
|
||||
(["--from", "-f", "_from"], "_from"),
|
||||
(["--return", "-r", "_ret"], "_ret"),
|
||||
],
|
||||
)
|
||||
def test_option_names(runner, option_args, expected):
|
||||
|
||||
@click.command()
|
||||
@click.option(*option_args, is_flag=True)
|
||||
def cmd(**kwargs):
|
||||
|
@ -484,6 +553,6 @@ def test_option_names(runner, option_args, expected):
|
|||
assert cmd.params[0].name == expected
|
||||
|
||||
for form in option_args:
|
||||
if form.startswith('-'):
|
||||
if form.startswith("-"):
|
||||
result = runner.invoke(cmd, [form])
|
||||
assert result.output == 'True\n'
|
||||
assert result.output == "True\n"
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import click
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
import click._termui_impl
|
||||
from click._compat import WIN
|
||||
|
||||
|
||||
class FakeClock(object):
|
||||
def __init__(self):
|
||||
|
@ -13,18 +18,26 @@ class FakeClock(object):
|
|||
return self.now
|
||||
|
||||
|
||||
def _create_progress(length=10, length_known=True, **kwargs):
|
||||
progress = click.progressbar(tuple(range(length)))
|
||||
for key, value in kwargs.items():
|
||||
setattr(progress, key, value)
|
||||
progress.length_known = length_known
|
||||
return progress
|
||||
|
||||
|
||||
def test_progressbar_strip_regression(runner, monkeypatch):
|
||||
fake_clock = FakeClock()
|
||||
label = ' padded line'
|
||||
label = " padded line"
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
with click.progressbar(tuple(range(10)), label=label) as progress:
|
||||
for thing in progress:
|
||||
with _create_progress(label=label) as progress:
|
||||
for _ in progress:
|
||||
fake_clock.advance_time()
|
||||
|
||||
monkeypatch.setattr(time, 'time', fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
|
||||
monkeypatch.setattr(time, "time", fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
|
||||
assert label in runner.invoke(cli, []).output
|
||||
|
||||
|
||||
|
@ -51,56 +64,198 @@ def test_progressbar_length_hint(runner, monkeypatch):
|
|||
|
||||
@click.command()
|
||||
def cli():
|
||||
with click.progressbar(Hinted(10), label='test') as progress:
|
||||
for thing in progress:
|
||||
with click.progressbar(Hinted(10), label="test") as progress:
|
||||
for _ in progress:
|
||||
fake_clock.advance_time()
|
||||
|
||||
monkeypatch.setattr(time, 'time', fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
|
||||
monkeypatch.setattr(time, "time", fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
def test_progressbar_hidden(runner, monkeypatch):
|
||||
fake_clock = FakeClock()
|
||||
label = 'whatever'
|
||||
label = "whatever"
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
with click.progressbar(tuple(range(10)), label=label) as progress:
|
||||
for thing in progress:
|
||||
with _create_progress(label=label) as progress:
|
||||
for _ in progress:
|
||||
fake_clock.advance_time()
|
||||
|
||||
monkeypatch.setattr(time, 'time', fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: False)
|
||||
assert runner.invoke(cli, []).output == ''
|
||||
monkeypatch.setattr(time, "time", fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, "isatty", lambda _: False)
|
||||
assert runner.invoke(cli, []).output == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize("avg, expected", [([], 0.0), ([1, 4], 2.5)])
|
||||
def test_progressbar_time_per_iteration(runner, avg, expected):
|
||||
with _create_progress(2, avg=avg) as progress:
|
||||
assert progress.time_per_iteration == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("finished, expected", [(False, 5), (True, 0)])
|
||||
def test_progressbar_eta(runner, finished, expected):
|
||||
with _create_progress(2, finished=finished, avg=[1, 4]) as progress:
|
||||
assert progress.eta == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"eta, expected",
|
||||
[
|
||||
(0, "00:00:00"),
|
||||
(30, "00:00:30"),
|
||||
(90, "00:01:30"),
|
||||
(900, "00:15:00"),
|
||||
(9000, "02:30:00"),
|
||||
(99999999999, "1157407d 09:46:39"),
|
||||
(None, ""),
|
||||
],
|
||||
)
|
||||
def test_progressbar_format_eta(runner, eta, expected):
|
||||
with _create_progress(1, eta_known=eta is not None, avg=[eta]) as progress:
|
||||
assert progress.format_eta() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pos, length", [(0, 5), (-1, 1), (5, 5), (6, 5), (4, 0)])
|
||||
def test_progressbar_format_pos(runner, pos, length):
|
||||
with _create_progress(length, length_known=length != 0, pos=pos) as progress:
|
||||
result = progress.format_pos()
|
||||
if progress.length_known:
|
||||
assert result == "{}/{}".format(pos, length)
|
||||
else:
|
||||
assert result == str(pos)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"length, finished, pos, avg, expected",
|
||||
[
|
||||
(8, False, 7, 0, "#######-"),
|
||||
(0, True, 8, 0, "########"),
|
||||
(0, False, 8, 0, "--------"),
|
||||
(0, False, 5, 3, "#-------"),
|
||||
],
|
||||
)
|
||||
def test_progressbar_format_bar(runner, length, finished, pos, avg, expected):
|
||||
with _create_progress(
|
||||
length, length_known=length != 0, width=8, pos=pos, finished=finished, avg=[avg]
|
||||
) as progress:
|
||||
assert progress.format_bar() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"length, length_known, show_percent, show_pos, pos, expected",
|
||||
[
|
||||
(0, True, True, True, 0, " [--------] 0/0 0%"),
|
||||
(0, True, False, True, 0, " [--------] 0/0"),
|
||||
(0, True, False, False, 0, " [--------]"),
|
||||
(0, False, False, False, 0, " [--------]"),
|
||||
(8, True, True, True, 8, " [########] 8/8 100%"),
|
||||
],
|
||||
)
|
||||
def test_progressbar_format_progress_line(
|
||||
runner, length, length_known, show_percent, show_pos, pos, expected
|
||||
):
|
||||
with _create_progress(
|
||||
length,
|
||||
length_known,
|
||||
width=8,
|
||||
show_percent=show_percent,
|
||||
pos=pos,
|
||||
show_pos=show_pos,
|
||||
) as progress:
|
||||
assert progress.format_progress_line() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_item", ["test", None])
|
||||
def test_progressbar_format_progress_line_with_show_func(runner, test_item):
|
||||
def item_show_func(item):
|
||||
return item
|
||||
|
||||
with _create_progress(
|
||||
item_show_func=item_show_func, current_item=test_item
|
||||
) as progress:
|
||||
if test_item:
|
||||
assert progress.format_progress_line().endswith(test_item)
|
||||
else:
|
||||
assert progress.format_progress_line().endswith(progress.format_pct())
|
||||
|
||||
|
||||
def test_progressbar_init_exceptions(runner):
|
||||
with pytest.raises(TypeError, match="iterable or length is required"):
|
||||
click.progressbar()
|
||||
|
||||
|
||||
def test_progressbar_iter_outside_with_exceptions(runner):
|
||||
with pytest.raises(RuntimeError, match="with block"):
|
||||
progress = click.progressbar(length=2)
|
||||
iter(progress)
|
||||
|
||||
|
||||
def test_progressbar_is_iterator(runner, monkeypatch):
|
||||
fake_clock = FakeClock()
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
with click.progressbar(range(10), label="test") as progress:
|
||||
while True:
|
||||
try:
|
||||
next(progress)
|
||||
fake_clock.advance_time()
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
monkeypatch.setattr(time, "time", fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
def test_choices_list_in_prompt(runner, monkeypatch):
|
||||
@click.command()
|
||||
@click.option('-g', type=click.Choice(['none', 'day', 'week', 'month']),
|
||||
prompt=True)
|
||||
@click.option(
|
||||
"-g", type=click.Choice(["none", "day", "week", "month"]), prompt=True
|
||||
)
|
||||
def cli_with_choices(g):
|
||||
pass
|
||||
|
||||
@click.command()
|
||||
@click.option('-g', type=click.Choice(['none', 'day', 'week', 'month']),
|
||||
prompt=True, show_choices=False)
|
||||
@click.option(
|
||||
"-g",
|
||||
type=click.Choice(["none", "day", "week", "month"]),
|
||||
prompt=True,
|
||||
show_choices=False,
|
||||
)
|
||||
def cli_without_choices(g):
|
||||
pass
|
||||
|
||||
result = runner.invoke(cli_with_choices, [], input='none')
|
||||
assert '(none, day, week, month)' in result.output
|
||||
result = runner.invoke(cli_with_choices, [], input="none")
|
||||
assert "(none, day, week, month)" in result.output
|
||||
|
||||
result = runner.invoke(cli_without_choices, [], input='none')
|
||||
assert '(none, day, week, month)' not in result.output
|
||||
result = runner.invoke(cli_without_choices, [], input="none")
|
||||
assert "(none, day, week, month)" not in result.output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_kwargs", [{"mode": "rt"}, {"mode": "rb"}, {"lazy": True}]
|
||||
)
|
||||
def test_file_prompt_default_format(runner, file_kwargs):
|
||||
@click.command()
|
||||
@click.option("-f", default=__file__, prompt="file", type=click.File(**file_kwargs))
|
||||
def cli(f):
|
||||
click.echo(f.name)
|
||||
|
||||
result = runner.invoke(cli)
|
||||
assert result.output == "file [{0}]: \n{0}\n".format(__file__)
|
||||
|
||||
|
||||
def test_secho(runner):
|
||||
with runner.isolation() as outstreams:
|
||||
click.secho(None, nl=False)
|
||||
bytes = outstreams[0].getvalue()
|
||||
assert bytes == b''
|
||||
assert bytes == b""
|
||||
|
||||
|
||||
def test_progressbar_yields_all_items(runner):
|
||||
|
@ -118,13 +273,50 @@ def test_progressbar_update(runner, monkeypatch):
|
|||
fake_clock.advance_time()
|
||||
print("")
|
||||
|
||||
monkeypatch.setattr(time, 'time', fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
|
||||
monkeypatch.setattr(time, "time", fake_clock.time)
|
||||
monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
|
||||
output = runner.invoke(cli, []).output
|
||||
|
||||
lines = [line for line in output.split('\n') if '[' in line]
|
||||
lines = [line for line in output.split("\n") if "[" in line]
|
||||
|
||||
assert ' 25% 00:00:03' in lines[0]
|
||||
assert ' 50% 00:00:02' in lines[1]
|
||||
assert ' 75% 00:00:01' in lines[2]
|
||||
assert '100% ' in lines[3]
|
||||
assert " 25% 00:00:03" in lines[0]
|
||||
assert " 50% 00:00:02" in lines[1]
|
||||
assert " 75% 00:00:01" in lines[2]
|
||||
assert "100% " in lines[3]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key_char", (u"h", u"H", u"é", u"À", u" ", u"字", u"àH", u"àR"))
|
||||
@pytest.mark.parametrize("echo", [True, False])
|
||||
@pytest.mark.skipif(not WIN, reason="Tests user-input using the msvcrt module.")
|
||||
def test_getchar_windows(runner, monkeypatch, key_char, echo):
|
||||
monkeypatch.setattr(click._termui_impl.msvcrt, "getwche", lambda: key_char)
|
||||
monkeypatch.setattr(click._termui_impl.msvcrt, "getwch", lambda: key_char)
|
||||
monkeypatch.setattr(click.termui, "_getchar", None)
|
||||
assert click.getchar(echo) == key_char
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"special_key_char, key_char", [(u"\x00", "a"), (u"\x00", "b"), (u"\xe0", "c")]
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
not WIN, reason="Tests special character inputs using the msvcrt module."
|
||||
)
|
||||
def test_getchar_special_key_windows(runner, monkeypatch, special_key_char, key_char):
|
||||
ordered_inputs = [key_char, special_key_char]
|
||||
monkeypatch.setattr(
|
||||
click._termui_impl.msvcrt, "getwch", lambda: ordered_inputs.pop()
|
||||
)
|
||||
monkeypatch.setattr(click.termui, "_getchar", None)
|
||||
assert click.getchar() == special_key_char + key_char
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key_char", "exc"), [(u"\x03", KeyboardInterrupt), (u"\x1a", EOFError)],
|
||||
)
|
||||
@pytest.mark.skipif(not WIN, reason="Tests user-input using the msvcrt module.")
|
||||
def test_getchar_windows_exceptions(runner, monkeypatch, key_char, exc):
|
||||
monkeypatch.setattr(click._termui_impl.msvcrt, "getwch", lambda: key_char)
|
||||
monkeypatch.setattr(click.termui, "_getchar", None)
|
||||
|
||||
with pytest.raises(exc):
|
||||
click.getchar()
|
||||
|
|
|
@ -2,12 +2,12 @@ import os
|
|||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import click
|
||||
|
||||
from click._compat import PY2
|
||||
from click._compat import WIN
|
||||
from click.testing import CliRunner
|
||||
|
||||
from click._compat import PY2, WIN
|
||||
|
||||
# Use the most reasonable io that users would use for the python version.
|
||||
if PY2:
|
||||
from cStringIO import StringIO as ReasonableBytesIO
|
||||
|
@ -18,8 +18,8 @@ else:
|
|||
def test_runner():
|
||||
@click.command()
|
||||
def test():
|
||||
i = click.get_binary_stream('stdin')
|
||||
o = click.get_binary_stream('stdout')
|
||||
i = click.get_binary_stream("stdin")
|
||||
o = click.get_binary_stream("stdout")
|
||||
while 1:
|
||||
chunk = i.read(4096)
|
||||
if not chunk:
|
||||
|
@ -28,21 +28,21 @@ def test_runner():
|
|||
o.flush()
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(test, input='Hello World!\n')
|
||||
result = runner.invoke(test, input="Hello World!\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Hello World!\n'
|
||||
assert result.output == "Hello World!\n"
|
||||
|
||||
runner = CliRunner(echo_stdin=True)
|
||||
result = runner.invoke(test, input='Hello World!\n')
|
||||
result = runner.invoke(test, input="Hello World!\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Hello World!\nHello World!\n'
|
||||
assert result.output == "Hello World!\nHello World!\n"
|
||||
|
||||
|
||||
def test_runner_with_stream():
|
||||
@click.command()
|
||||
def test():
|
||||
i = click.get_binary_stream('stdin')
|
||||
o = click.get_binary_stream('stdout')
|
||||
i = click.get_binary_stream("stdin")
|
||||
o = click.get_binary_stream("stdout")
|
||||
while 1:
|
||||
chunk = i.read(4096)
|
||||
if not chunk:
|
||||
|
@ -51,36 +51,36 @@ def test_runner_with_stream():
|
|||
o.flush()
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(test, input=ReasonableBytesIO(b'Hello World!\n'))
|
||||
result = runner.invoke(test, input=ReasonableBytesIO(b"Hello World!\n"))
|
||||
assert not result.exception
|
||||
assert result.output == 'Hello World!\n'
|
||||
assert result.output == "Hello World!\n"
|
||||
|
||||
runner = CliRunner(echo_stdin=True)
|
||||
result = runner.invoke(test, input=ReasonableBytesIO(b'Hello World!\n'))
|
||||
result = runner.invoke(test, input=ReasonableBytesIO(b"Hello World!\n"))
|
||||
assert not result.exception
|
||||
assert result.output == 'Hello World!\nHello World!\n'
|
||||
assert result.output == "Hello World!\nHello World!\n"
|
||||
|
||||
|
||||
def test_prompts():
|
||||
@click.command()
|
||||
@click.option('--foo', prompt=True)
|
||||
@click.option("--foo", prompt=True)
|
||||
def test(foo):
|
||||
click.echo('foo=%s' % foo)
|
||||
click.echo("foo={}".format(foo))
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(test, input='wau wau\n')
|
||||
result = runner.invoke(test, input="wau wau\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo: wau wau\nfoo=wau wau\n'
|
||||
assert result.output == "Foo: wau wau\nfoo=wau wau\n"
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', prompt=True, hide_input=True)
|
||||
@click.option("--foo", prompt=True, hide_input=True)
|
||||
def test(foo):
|
||||
click.echo('foo=%s' % foo)
|
||||
click.echo("foo={}".format(foo))
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(test, input='wau wau\n')
|
||||
result = runner.invoke(test, input="wau wau\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo: \nfoo=wau wau\n'
|
||||
assert result.output == "Foo: \nfoo=wau wau\n"
|
||||
|
||||
|
||||
def test_getchar():
|
||||
|
@ -89,9 +89,9 @@ def test_getchar():
|
|||
click.echo(click.getchar())
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(continue_it, input='y')
|
||||
result = runner.invoke(continue_it, input="y")
|
||||
assert not result.exception
|
||||
assert result.output == 'y\n'
|
||||
assert result.output == "y\n"
|
||||
|
||||
|
||||
def test_catch_exceptions():
|
||||
|
@ -118,20 +118,20 @@ def test_catch_exceptions():
|
|||
assert result.exit_code == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
||||
@pytest.mark.skipif(WIN, reason="Test does not make sense on Windows.")
|
||||
def test_with_color():
|
||||
@click.command()
|
||||
def cli():
|
||||
click.secho('hello world', fg='blue')
|
||||
click.secho("hello world", fg="blue")
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(cli)
|
||||
assert result.output == 'hello world\n'
|
||||
assert result.output == "hello world\n"
|
||||
assert not result.exception
|
||||
|
||||
result = runner.invoke(cli, color=True)
|
||||
assert result.output == click.style('hello world', fg='blue') + '\n'
|
||||
assert result.output == "{}\n".format(click.style("hello world", fg="blue"))
|
||||
assert not result.exception
|
||||
|
||||
|
||||
|
@ -143,93 +143,93 @@ def test_with_color_but_pause_not_blocking():
|
|||
runner = CliRunner()
|
||||
result = runner.invoke(cli, color=True)
|
||||
assert not result.exception
|
||||
assert result.output == ''
|
||||
assert result.output == ""
|
||||
|
||||
|
||||
def test_exit_code_and_output_from_sys_exit():
|
||||
# See issue #362
|
||||
@click.command()
|
||||
def cli_string():
|
||||
click.echo('hello world')
|
||||
sys.exit('error')
|
||||
click.echo("hello world")
|
||||
sys.exit("error")
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli_string_ctx_exit(ctx):
|
||||
click.echo('hello world')
|
||||
ctx.exit('error')
|
||||
click.echo("hello world")
|
||||
ctx.exit("error")
|
||||
|
||||
@click.command()
|
||||
def cli_int():
|
||||
click.echo('hello world')
|
||||
click.echo("hello world")
|
||||
sys.exit(1)
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli_int_ctx_exit(ctx):
|
||||
click.echo('hello world')
|
||||
click.echo("hello world")
|
||||
ctx.exit(1)
|
||||
|
||||
@click.command()
|
||||
def cli_float():
|
||||
click.echo('hello world')
|
||||
click.echo("hello world")
|
||||
sys.exit(1.0)
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli_float_ctx_exit(ctx):
|
||||
click.echo('hello world')
|
||||
click.echo("hello world")
|
||||
ctx.exit(1.0)
|
||||
|
||||
@click.command()
|
||||
def cli_no_error():
|
||||
click.echo('hello world')
|
||||
click.echo("hello world")
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(cli_string)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\nerror\n'
|
||||
assert result.output == "hello world\nerror\n"
|
||||
|
||||
result = runner.invoke(cli_string_ctx_exit)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\nerror\n'
|
||||
assert result.output == "hello world\nerror\n"
|
||||
|
||||
result = runner.invoke(cli_int)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\n'
|
||||
assert result.output == "hello world\n"
|
||||
|
||||
result = runner.invoke(cli_int_ctx_exit)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\n'
|
||||
assert result.output == "hello world\n"
|
||||
|
||||
result = runner.invoke(cli_float)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\n1.0\n'
|
||||
assert result.output == "hello world\n1.0\n"
|
||||
|
||||
result = runner.invoke(cli_float_ctx_exit)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\n1.0\n'
|
||||
assert result.output == "hello world\n1.0\n"
|
||||
|
||||
result = runner.invoke(cli_no_error)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'hello world\n'
|
||||
assert result.output == "hello world\n"
|
||||
|
||||
|
||||
def test_env():
|
||||
@click.command()
|
||||
def cli_env():
|
||||
click.echo('ENV=%s' % os.environ['TEST_CLICK_ENV'])
|
||||
click.echo("ENV={}".format(os.environ["TEST_CLICK_ENV"]))
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
env_orig = dict(os.environ)
|
||||
env = dict(env_orig)
|
||||
assert 'TEST_CLICK_ENV' not in env
|
||||
env['TEST_CLICK_ENV'] = 'some_value'
|
||||
assert "TEST_CLICK_ENV" not in env
|
||||
env["TEST_CLICK_ENV"] = "some_value"
|
||||
result = runner.invoke(cli_env, env=env)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'ENV=some_value\n'
|
||||
assert result.output == "ENV=some_value\n"
|
||||
|
||||
assert os.environ == env_orig
|
||||
|
||||
|
@ -244,31 +244,45 @@ def test_stderr():
|
|||
|
||||
result = runner.invoke(cli_stderr)
|
||||
|
||||
assert result.output == 'stdout\n'
|
||||
assert result.stdout == 'stdout\n'
|
||||
assert result.stderr == 'stderr\n'
|
||||
assert result.output == "stdout\n"
|
||||
assert result.stdout == "stdout\n"
|
||||
assert result.stderr == "stderr\n"
|
||||
|
||||
runner_mix = CliRunner(mix_stderr=True)
|
||||
result_mix = runner_mix.invoke(cli_stderr)
|
||||
|
||||
assert result_mix.output == 'stdout\nstderr\n'
|
||||
assert result_mix.stdout == 'stdout\nstderr\n'
|
||||
assert result_mix.output == "stdout\nstderr\n"
|
||||
assert result_mix.stdout == "stdout\nstderr\n"
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
result_mix.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize('args, expected_output', [
|
||||
(None, 'bar\n'),
|
||||
([], 'bar\n'),
|
||||
('', 'bar\n'),
|
||||
(['--foo', 'one two'], 'one two\n'),
|
||||
('--foo "one two"', 'one two\n'),
|
||||
])
|
||||
def test_args(args, expected_output):
|
||||
|
||||
@click.command()
|
||||
@click.option('--foo', default='bar')
|
||||
def cli_empty_stderr():
|
||||
click.echo("stdout")
|
||||
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
|
||||
result = runner.invoke(cli_empty_stderr)
|
||||
|
||||
assert result.output == "stdout\n"
|
||||
assert result.stdout == "stdout\n"
|
||||
assert result.stderr == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, expected_output",
|
||||
[
|
||||
(None, "bar\n"),
|
||||
([], "bar\n"),
|
||||
("", "bar\n"),
|
||||
(["--foo", "one two"], "one two\n"),
|
||||
('--foo "one two"', "one two\n"),
|
||||
],
|
||||
)
|
||||
def test_args(args, expected_output):
|
||||
@click.command()
|
||||
@click.option("--foo", default="bar")
|
||||
def cli_args(foo):
|
||||
click.echo(foo)
|
||||
|
||||
|
@ -286,4 +300,4 @@ def test_setting_prog_name_in_extra():
|
|||
runner = CliRunner()
|
||||
result = runner.invoke(cli, prog_name="foobar")
|
||||
assert not result.exception
|
||||
assert result.output == 'ok\n'
|
||||
assert result.output == "ok\n"
|
||||
|
|
|
@ -1,173 +1,187 @@
|
|||
import os
|
||||
import stat
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import click
|
||||
import click.utils
|
||||
import click._termui_impl
|
||||
from click._compat import WIN, PY2
|
||||
import click.utils
|
||||
from click._compat import WIN
|
||||
|
||||
|
||||
def test_echo(runner):
|
||||
with runner.isolation() as outstreams:
|
||||
click.echo(u'\N{SNOWMAN}')
|
||||
click.echo(b'\x44\x44')
|
||||
click.echo(u"\N{SNOWMAN}")
|
||||
click.echo(b"\x44\x44")
|
||||
click.echo(42, nl=False)
|
||||
click.echo(b'a', nl=False)
|
||||
click.echo('\x1b[31mx\x1b[39m', nl=False)
|
||||
bytes = outstreams[0].getvalue().replace(b'\r\n', b'\n')
|
||||
assert bytes == b'\xe2\x98\x83\nDD\n42ax'
|
||||
click.echo(b"a", nl=False)
|
||||
click.echo("\x1b[31mx\x1b[39m", nl=False)
|
||||
bytes = outstreams[0].getvalue().replace(b"\r\n", b"\n")
|
||||
assert bytes == b"\xe2\x98\x83\nDD\n42ax"
|
||||
|
||||
# If we are in Python 2, we expect that writing bytes into a string io
|
||||
# does not do anything crazy. In Python 3
|
||||
if sys.version_info[0] == 2:
|
||||
import StringIO
|
||||
|
||||
sys.stdout = x = StringIO.StringIO()
|
||||
try:
|
||||
click.echo('\xf6')
|
||||
click.echo("\xf6")
|
||||
finally:
|
||||
sys.stdout = sys.__stdout__
|
||||
assert x.getvalue() == '\xf6\n'
|
||||
assert x.getvalue() == "\xf6\n"
|
||||
|
||||
# And in any case, if wrapped, we expect bytes to survive.
|
||||
@click.command()
|
||||
def cli():
|
||||
click.echo(b'\xf6')
|
||||
click.echo(b"\xf6")
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.stdout_bytes == b'\xf6\n'
|
||||
assert result.stdout_bytes == b"\xf6\n"
|
||||
|
||||
# Ensure we do not strip for bytes.
|
||||
with runner.isolation() as outstreams:
|
||||
click.echo(bytearray(b'\x1b[31mx\x1b[39m'), nl=False)
|
||||
assert outstreams[0].getvalue() == b'\x1b[31mx\x1b[39m'
|
||||
click.echo(bytearray(b"\x1b[31mx\x1b[39m"), nl=False)
|
||||
assert outstreams[0].getvalue() == b"\x1b[31mx\x1b[39m"
|
||||
|
||||
|
||||
def test_echo_custom_file():
|
||||
import io
|
||||
|
||||
f = io.StringIO()
|
||||
click.echo(u'hello', file=f)
|
||||
assert f.getvalue() == u'hello\n'
|
||||
click.echo(u"hello", file=f)
|
||||
assert f.getvalue() == u"hello\n"
|
||||
|
||||
|
||||
def test_styling():
|
||||
examples = [
|
||||
('x', dict(fg='black'), '\x1b[30mx\x1b[0m'),
|
||||
('x', dict(fg='red'), '\x1b[31mx\x1b[0m'),
|
||||
('x', dict(fg='green'), '\x1b[32mx\x1b[0m'),
|
||||
('x', dict(fg='yellow'), '\x1b[33mx\x1b[0m'),
|
||||
('x', dict(fg='blue'), '\x1b[34mx\x1b[0m'),
|
||||
('x', dict(fg='magenta'), '\x1b[35mx\x1b[0m'),
|
||||
('x', dict(fg='cyan'), '\x1b[36mx\x1b[0m'),
|
||||
('x', dict(fg='white'), '\x1b[37mx\x1b[0m'),
|
||||
('x', dict(bg='black'), '\x1b[40mx\x1b[0m'),
|
||||
('x', dict(bg='red'), '\x1b[41mx\x1b[0m'),
|
||||
('x', dict(bg='green'), '\x1b[42mx\x1b[0m'),
|
||||
('x', dict(bg='yellow'), '\x1b[43mx\x1b[0m'),
|
||||
('x', dict(bg='blue'), '\x1b[44mx\x1b[0m'),
|
||||
('x', dict(bg='magenta'), '\x1b[45mx\x1b[0m'),
|
||||
('x', dict(bg='cyan'), '\x1b[46mx\x1b[0m'),
|
||||
('x', dict(bg='white'), '\x1b[47mx\x1b[0m'),
|
||||
('foo bar', dict(blink=True), '\x1b[5mfoo bar\x1b[0m'),
|
||||
('foo bar', dict(underline=True), '\x1b[4mfoo bar\x1b[0m'),
|
||||
('foo bar', dict(bold=True), '\x1b[1mfoo bar\x1b[0m'),
|
||||
('foo bar', dict(dim=True), '\x1b[2mfoo bar\x1b[0m'),
|
||||
]
|
||||
for text, styles, ref in examples:
|
||||
assert click.style(text, **styles) == ref
|
||||
assert click.unstyle(ref) == text
|
||||
@pytest.mark.parametrize(
|
||||
("styles", "ref"),
|
||||
[
|
||||
({"fg": "black"}, "\x1b[30mx y\x1b[0m"),
|
||||
({"fg": "red"}, "\x1b[31mx y\x1b[0m"),
|
||||
({"fg": "green"}, "\x1b[32mx y\x1b[0m"),
|
||||
({"fg": "yellow"}, "\x1b[33mx y\x1b[0m"),
|
||||
({"fg": "blue"}, "\x1b[34mx y\x1b[0m"),
|
||||
({"fg": "magenta"}, "\x1b[35mx y\x1b[0m"),
|
||||
({"fg": "cyan"}, "\x1b[36mx y\x1b[0m"),
|
||||
({"fg": "white"}, "\x1b[37mx y\x1b[0m"),
|
||||
({"bg": "black"}, "\x1b[40mx y\x1b[0m"),
|
||||
({"bg": "red"}, "\x1b[41mx y\x1b[0m"),
|
||||
({"bg": "green"}, "\x1b[42mx y\x1b[0m"),
|
||||
({"bg": "yellow"}, "\x1b[43mx y\x1b[0m"),
|
||||
({"bg": "blue"}, "\x1b[44mx y\x1b[0m"),
|
||||
({"bg": "magenta"}, "\x1b[45mx y\x1b[0m"),
|
||||
({"bg": "cyan"}, "\x1b[46mx y\x1b[0m"),
|
||||
({"bg": "white"}, "\x1b[47mx y\x1b[0m"),
|
||||
({"blink": True}, "\x1b[5mx y\x1b[0m"),
|
||||
({"underline": True}, "\x1b[4mx y\x1b[0m"),
|
||||
({"bold": True}, "\x1b[1mx y\x1b[0m"),
|
||||
({"dim": True}, "\x1b[2mx y\x1b[0m"),
|
||||
],
|
||||
)
|
||||
def test_styling(styles, ref):
|
||||
assert click.style("x y", **styles) == ref
|
||||
assert click.unstyle(ref) == "x y"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("text", "expect"), [("\x1b[?25lx y\x1b[?25h", "x y")])
|
||||
def test_unstyle_other_ansi(text, expect):
|
||||
assert click.unstyle(text) == expect
|
||||
|
||||
|
||||
def test_filename_formatting():
|
||||
assert click.format_filename(b'foo.txt') == 'foo.txt'
|
||||
assert click.format_filename(b'/x/foo.txt') == '/x/foo.txt'
|
||||
assert click.format_filename(u'/x/foo.txt') == '/x/foo.txt'
|
||||
assert click.format_filename(u'/x/foo.txt', shorten=True) == 'foo.txt'
|
||||
assert click.format_filename(b"foo.txt") == "foo.txt"
|
||||
assert click.format_filename(b"/x/foo.txt") == "/x/foo.txt"
|
||||
assert click.format_filename(u"/x/foo.txt") == "/x/foo.txt"
|
||||
assert click.format_filename(u"/x/foo.txt", shorten=True) == "foo.txt"
|
||||
|
||||
# filesystem encoding on windows permits this.
|
||||
if not WIN:
|
||||
assert click.format_filename(b'/x/foo\xff.txt', shorten=True) \
|
||||
== u'foo\ufffd.txt'
|
||||
assert (
|
||||
click.format_filename(b"/x/foo\xff.txt", shorten=True) == u"foo\ufffd.txt"
|
||||
)
|
||||
|
||||
|
||||
def test_prompts(runner):
|
||||
@click.command()
|
||||
def test():
|
||||
if click.confirm('Foo'):
|
||||
click.echo('yes!')
|
||||
if click.confirm("Foo"):
|
||||
click.echo("yes!")
|
||||
else:
|
||||
click.echo('no :(')
|
||||
click.echo("no :(")
|
||||
|
||||
result = runner.invoke(test, input='y\n')
|
||||
result = runner.invoke(test, input="y\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo [y/N]: y\nyes!\n'
|
||||
assert result.output == "Foo [y/N]: y\nyes!\n"
|
||||
|
||||
result = runner.invoke(test, input='\n')
|
||||
result = runner.invoke(test, input="\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo [y/N]: \nno :(\n'
|
||||
assert result.output == "Foo [y/N]: \nno :(\n"
|
||||
|
||||
result = runner.invoke(test, input='n\n')
|
||||
result = runner.invoke(test, input="n\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo [y/N]: n\nno :(\n'
|
||||
assert result.output == "Foo [y/N]: n\nno :(\n"
|
||||
|
||||
@click.command()
|
||||
def test_no():
|
||||
if click.confirm('Foo', default=True):
|
||||
click.echo('yes!')
|
||||
if click.confirm("Foo", default=True):
|
||||
click.echo("yes!")
|
||||
else:
|
||||
click.echo('no :(')
|
||||
click.echo("no :(")
|
||||
|
||||
result = runner.invoke(test_no, input='y\n')
|
||||
result = runner.invoke(test_no, input="y\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo [Y/n]: y\nyes!\n'
|
||||
assert result.output == "Foo [Y/n]: y\nyes!\n"
|
||||
|
||||
result = runner.invoke(test_no, input='\n')
|
||||
result = runner.invoke(test_no, input="\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo [Y/n]: \nyes!\n'
|
||||
assert result.output == "Foo [Y/n]: \nyes!\n"
|
||||
|
||||
result = runner.invoke(test_no, input='n\n')
|
||||
result = runner.invoke(test_no, input="n\n")
|
||||
assert not result.exception
|
||||
assert result.output == 'Foo [Y/n]: n\nno :(\n'
|
||||
assert result.output == "Foo [Y/n]: n\nno :(\n"
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
|
||||
@pytest.mark.skipif(WIN, reason="Different behavior on windows.")
|
||||
def test_prompts_abort(monkeypatch, capsys):
|
||||
def f(_):
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
monkeypatch.setattr('click.termui.hidden_prompt_func', f)
|
||||
monkeypatch.setattr("click.termui.hidden_prompt_func", f)
|
||||
|
||||
try:
|
||||
click.prompt('Password', hide_input=True)
|
||||
click.prompt("Password", hide_input=True)
|
||||
except click.Abort:
|
||||
click.echo('Screw you.')
|
||||
click.echo("Screw you.")
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert out == 'Password: \nScrew you.\n'
|
||||
assert out == "Password: \nScrew you.\n"
|
||||
|
||||
|
||||
def _test_gen_func():
|
||||
yield 'a'
|
||||
yield 'b'
|
||||
yield 'c'
|
||||
yield 'abc'
|
||||
yield "a"
|
||||
yield "b"
|
||||
yield "c"
|
||||
yield "abc"
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
|
||||
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
|
||||
@pytest.mark.parametrize('test', [
|
||||
# We need lambda here, because pytest will
|
||||
# reuse the parameters, and then the generators
|
||||
# are already used and will not yield anymore
|
||||
('just text\n', lambda: 'just text'),
|
||||
('iterable\n', lambda: ["itera", "ble"]),
|
||||
('abcabc\n', lambda: _test_gen_func),
|
||||
('abcabc\n', lambda: _test_gen_func()),
|
||||
('012345\n', lambda: (c for c in range(6))),
|
||||
])
|
||||
@pytest.mark.skipif(WIN, reason="Different behavior on windows.")
|
||||
@pytest.mark.parametrize("cat", ["cat", "cat ", "cat "])
|
||||
@pytest.mark.parametrize(
|
||||
"test",
|
||||
[
|
||||
# We need lambda here, because pytest will
|
||||
# reuse the parameters, and then the generators
|
||||
# are already used and will not yield anymore
|
||||
("just text\n", lambda: "just text"),
|
||||
("iterable\n", lambda: ["itera", "ble"]),
|
||||
("abcabc\n", lambda: _test_gen_func),
|
||||
("abcabc\n", lambda: _test_gen_func()),
|
||||
("012345\n", lambda: (c for c in range(6))),
|
||||
],
|
||||
)
|
||||
def test_echo_via_pager(monkeypatch, capfd, cat, test):
|
||||
monkeypatch.setitem(os.environ, 'PAGER', cat)
|
||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda x: True)
|
||||
monkeypatch.setitem(os.environ, "PAGER", cat)
|
||||
monkeypatch.setattr(click._termui_impl, "isatty", lambda x: True)
|
||||
|
||||
expected_output = test[0]
|
||||
test_input = test[1]()
|
||||
|
@ -178,35 +192,35 @@ def test_echo_via_pager(monkeypatch, capfd, cat, test):
|
|||
assert out == expected_output
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
||||
@pytest.mark.skipif(WIN, reason="Test does not make sense on Windows.")
|
||||
def test_echo_color_flag(monkeypatch, capfd):
|
||||
isatty = True
|
||||
monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty)
|
||||
monkeypatch.setattr(click._compat, "isatty", lambda x: isatty)
|
||||
|
||||
text = 'foo'
|
||||
styled_text = click.style(text, fg='red')
|
||||
assert styled_text == '\x1b[31mfoo\x1b[0m'
|
||||
text = "foo"
|
||||
styled_text = click.style(text, fg="red")
|
||||
assert styled_text == "\x1b[31mfoo\x1b[0m"
|
||||
|
||||
click.echo(styled_text, color=False)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == text + '\n'
|
||||
assert out == "{}\n".format(text)
|
||||
|
||||
click.echo(styled_text, color=True)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == styled_text + '\n'
|
||||
assert out == "{}\n".format(styled_text)
|
||||
|
||||
isatty = True
|
||||
click.echo(styled_text)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == styled_text + '\n'
|
||||
assert out == "{}\n".format(styled_text)
|
||||
|
||||
isatty = False
|
||||
click.echo(styled_text)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == text + '\n'
|
||||
assert out == "{}\n".format(text)
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Test too complex to make work windows.')
|
||||
@pytest.mark.skipif(WIN, reason="Test too complex to make work windows.")
|
||||
def test_echo_writing_to_standard_error(capfd, monkeypatch):
|
||||
def emulate_input(text):
|
||||
"""Emulate keyboard input."""
|
||||
|
@ -214,92 +228,167 @@ def test_echo_writing_to_standard_error(capfd, monkeypatch):
|
|||
from StringIO import StringIO
|
||||
else:
|
||||
from io import StringIO
|
||||
monkeypatch.setattr(sys, 'stdin', StringIO(text))
|
||||
monkeypatch.setattr(sys, "stdin", StringIO(text))
|
||||
|
||||
click.echo('Echo to standard output')
|
||||
click.echo("Echo to standard output")
|
||||
out, err = capfd.readouterr()
|
||||
assert out == 'Echo to standard output\n'
|
||||
assert err == ''
|
||||
assert out == "Echo to standard output\n"
|
||||
assert err == ""
|
||||
|
||||
click.echo('Echo to standard error', err=True)
|
||||
click.echo("Echo to standard error", err=True)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == ''
|
||||
assert err == 'Echo to standard error\n'
|
||||
assert out == ""
|
||||
assert err == "Echo to standard error\n"
|
||||
|
||||
emulate_input('asdlkj\n')
|
||||
click.prompt('Prompt to stdin')
|
||||
emulate_input("asdlkj\n")
|
||||
click.prompt("Prompt to stdin")
|
||||
out, err = capfd.readouterr()
|
||||
assert out == 'Prompt to stdin: '
|
||||
assert err == ''
|
||||
assert out == "Prompt to stdin: "
|
||||
assert err == ""
|
||||
|
||||
emulate_input('asdlkj\n')
|
||||
click.prompt('Prompt to stderr', err=True)
|
||||
emulate_input("asdlkj\n")
|
||||
click.prompt("Prompt to stderr", err=True)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == ''
|
||||
assert err == 'Prompt to stderr: '
|
||||
assert out == ""
|
||||
assert err == "Prompt to stderr: "
|
||||
|
||||
emulate_input('y\n')
|
||||
click.confirm('Prompt to stdin')
|
||||
emulate_input("y\n")
|
||||
click.confirm("Prompt to stdin")
|
||||
out, err = capfd.readouterr()
|
||||
assert out == 'Prompt to stdin [y/N]: '
|
||||
assert err == ''
|
||||
assert out == "Prompt to stdin [y/N]: "
|
||||
assert err == ""
|
||||
|
||||
emulate_input('y\n')
|
||||
click.confirm('Prompt to stderr', err=True)
|
||||
emulate_input("y\n")
|
||||
click.confirm("Prompt to stderr", err=True)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == ''
|
||||
assert err == 'Prompt to stderr [y/N]: '
|
||||
assert out == ""
|
||||
assert err == "Prompt to stderr [y/N]: "
|
||||
|
||||
monkeypatch.setattr(click.termui, 'isatty', lambda x: True)
|
||||
monkeypatch.setattr(click.termui, 'getchar', lambda: ' ')
|
||||
monkeypatch.setattr(click.termui, "isatty", lambda x: True)
|
||||
monkeypatch.setattr(click.termui, "getchar", lambda: " ")
|
||||
|
||||
click.pause('Pause to stdout')
|
||||
click.pause("Pause to stdout")
|
||||
out, err = capfd.readouterr()
|
||||
assert out == 'Pause to stdout\n'
|
||||
assert err == ''
|
||||
assert out == "Pause to stdout\n"
|
||||
assert err == ""
|
||||
|
||||
click.pause('Pause to stderr', err=True)
|
||||
click.pause("Pause to stderr", err=True)
|
||||
out, err = capfd.readouterr()
|
||||
assert out == ''
|
||||
assert err == 'Pause to stderr\n'
|
||||
assert out == ""
|
||||
assert err == "Pause to stderr\n"
|
||||
|
||||
|
||||
def test_open_file(runner):
|
||||
@click.command()
|
||||
@click.argument("filename")
|
||||
def cli(filename):
|
||||
with click.open_file(filename) as f:
|
||||
click.echo(f.read())
|
||||
|
||||
click.echo("meep")
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
with open('hello.txt', 'w') as f:
|
||||
f.write('Cool stuff')
|
||||
with open("hello.txt", "w") as f:
|
||||
f.write("Cool stuff")
|
||||
|
||||
result = runner.invoke(cli, ["hello.txt"])
|
||||
assert result.exception is None
|
||||
assert result.output == "Cool stuff\nmeep\n"
|
||||
|
||||
result = runner.invoke(cli, ["-"], input="foobar")
|
||||
assert result.exception is None
|
||||
assert result.output == "foobar\nmeep\n"
|
||||
|
||||
|
||||
def test_open_file_ignore_errors_stdin(runner):
|
||||
@click.command()
|
||||
@click.argument("filename")
|
||||
def cli(filename):
|
||||
with click.open_file(filename, errors="ignore") as f:
|
||||
click.echo(f.read())
|
||||
|
||||
result = runner.invoke(cli, ["-"], input=os.urandom(16))
|
||||
assert result.exception is None
|
||||
|
||||
|
||||
def test_open_file_respects_ignore(runner):
|
||||
with runner.isolated_filesystem():
|
||||
with open("test.txt", "w") as f:
|
||||
f.write("Hello world!")
|
||||
|
||||
with click.open_file("test.txt", encoding="utf8", errors="ignore") as f:
|
||||
assert f.errors == "ignore"
|
||||
|
||||
|
||||
def test_open_file_ignore_invalid_utf8(runner):
|
||||
with runner.isolated_filesystem():
|
||||
with open("test.txt", "wb") as f:
|
||||
f.write(b"\xe2\x28\xa1")
|
||||
|
||||
with click.open_file("test.txt", encoding="utf8", errors="ignore") as f:
|
||||
f.read()
|
||||
|
||||
|
||||
def test_open_file_ignore_no_encoding(runner):
|
||||
with runner.isolated_filesystem():
|
||||
with open("test.bin", "wb") as f:
|
||||
f.write(os.urandom(16))
|
||||
|
||||
with click.open_file("test.bin", errors="ignore") as f:
|
||||
f.read()
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason="os.chmod() is not fully supported on Windows.")
|
||||
@pytest.mark.parametrize("permissions", [0o400, 0o444, 0o600, 0o644])
|
||||
def test_open_file_atomic_permissions_existing_file(runner, permissions):
|
||||
with runner.isolated_filesystem():
|
||||
with open("existing.txt", "w") as f:
|
||||
f.write("content")
|
||||
os.chmod("existing.txt", permissions)
|
||||
|
||||
@click.command()
|
||||
@click.argument('filename')
|
||||
@click.argument("filename")
|
||||
def cli(filename):
|
||||
with click.open_file(filename) as f:
|
||||
click.echo(f.read())
|
||||
click.echo('meep')
|
||||
click.open_file(filename, "w", atomic=True).close()
|
||||
|
||||
result = runner.invoke(cli, ['hello.txt'])
|
||||
result = runner.invoke(cli, ["existing.txt"])
|
||||
assert result.exception is None
|
||||
assert result.output == 'Cool stuff\nmeep\n'
|
||||
assert stat.S_IMODE(os.stat("existing.txt").st_mode) == permissions
|
||||
|
||||
result = runner.invoke(cli, ['-'], input='foobar')
|
||||
|
||||
@pytest.mark.skipif(WIN, reason="os.stat() is not fully supported on Windows.")
|
||||
def test_open_file_atomic_permissions_new_file(runner):
|
||||
with runner.isolated_filesystem():
|
||||
|
||||
@click.command()
|
||||
@click.argument("filename")
|
||||
def cli(filename):
|
||||
click.open_file(filename, "w", atomic=True).close()
|
||||
|
||||
# Create a test file to get the expected permissions for new files
|
||||
# according to the current umask.
|
||||
with open("test.txt", "w"):
|
||||
pass
|
||||
permissions = stat.S_IMODE(os.stat("test.txt").st_mode)
|
||||
|
||||
result = runner.invoke(cli, ["new.txt"])
|
||||
assert result.exception is None
|
||||
assert result.output == 'foobar\nmeep\n'
|
||||
assert stat.S_IMODE(os.stat("new.txt").st_mode) == permissions
|
||||
|
||||
|
||||
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
||||
def test_iter_keepopenfile(tmpdir):
|
||||
expected = list(map(str, range(10)))
|
||||
p = tmpdir.mkdir('testdir').join('testfile')
|
||||
p.write(os.linesep.join(expected))
|
||||
p = tmpdir.mkdir("testdir").join("testfile")
|
||||
p.write("\n".join(expected))
|
||||
with p.open() as f:
|
||||
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
|
||||
assert e_line == a_line.strip()
|
||||
|
||||
|
||||
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
||||
def test_iter_lazyfile(tmpdir):
|
||||
expected = list(map(str, range(10)))
|
||||
p = tmpdir.mkdir('testdir').join('testfile')
|
||||
p.write(os.linesep.join(expected))
|
||||
p = tmpdir.mkdir("testdir").join("testfile")
|
||||
p.write("\n".join(expected))
|
||||
with p.open() as f:
|
||||
with click.utils.LazyFile(f.name) as lf:
|
||||
for e_line, a_line in zip(expected, lf):
|
||||
|
|
38
tox.ini
38
tox.ini
|
@ -1,39 +1,21 @@
|
|||
[tox]
|
||||
envlist =
|
||||
py{37,36,35,34,27,py3,py}
|
||||
docs-html
|
||||
coverage-report
|
||||
py{38,37,36,35,27,py3,py}
|
||||
style
|
||||
docs
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
passenv = LANG
|
||||
deps =
|
||||
pytest
|
||||
coverage
|
||||
colorama
|
||||
commands = coverage run -p -m pytest {posargs}
|
||||
commands = pytest --tb=short --basetemp={envtmpdir} {posargs}
|
||||
|
||||
[testenv:docs-html]
|
||||
[testenv:style]
|
||||
deps = pre-commit
|
||||
skip_install = true
|
||||
commands = pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
[testenv:docs]
|
||||
deps = -r docs/requirements.txt
|
||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
|
||||
|
||||
[testenv:docs-linkcheck]
|
||||
deps = -r docs/requirements.txt
|
||||
commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs {envtmpdir}/linkcheck
|
||||
|
||||
[testenv:coverage-report]
|
||||
deps = coverage
|
||||
skip_install = true
|
||||
commands =
|
||||
coverage combine
|
||||
coverage report
|
||||
coverage html
|
||||
|
||||
[testenv:codecov]
|
||||
passenv = CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_*
|
||||
deps = codecov
|
||||
skip_install = true
|
||||
commands =
|
||||
coverage combine
|
||||
coverage report
|
||||
codecov
|
||||
|
|
Loading…
Reference in a new issue