From edb81f8510a60adeb9d721a3d6b1a76e053bf2a6 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 3 Jul 2023 20:24:41 -0700 Subject: [PATCH 01/17] Separate out virtual env --- docs/index.rst | 1 + docs/quickstart.rst | 61 +++------------------------------------------ docs/virtualenv.rst | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 57 deletions(-) create mode 100644 docs/virtualenv.rst diff --git a/docs/index.rst b/docs/index.rst index ad965a36b..850b2625a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -68,6 +68,7 @@ usage patterns. why quickstart + virtualenv setuptools parameters options diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 7cd26ca06..1ed00622a 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,66 +3,13 @@ Quickstart .. currentmodule:: click -You can get the library directly from PyPI:: +Install +---------------------- +Install from PyPI:: pip install click -The installation into a :ref:`virtualenv` is heavily recommended. - -.. _virtualenv: - -virtualenv ----------- - -Virtualenv is probably what you want to use for developing Click -applications. - -What problem does virtualenv solve? Chances are that you want to use it -for other projects besides your Click script. But the more projects you -have, the more likely it is that you will be working with different -versions of Python itself, or at least different versions of Python -libraries. Let's face it: quite often libraries break backwards -compatibility, and it's unlikely that any serious application will have -zero dependencies. So what do you do if two or more of your projects have -conflicting dependencies? - -Virtualenv to the rescue! Virtualenv enables multiple side-by-side -installations of Python, one for each project. It doesn't actually -install separate copies of Python, but it does provide a clever way to -keep different project environments isolated. - -Create your project folder, then a virtualenv within it:: - - $ mkdir myproject - $ cd myproject - $ python3 -m venv .venv - -Now, whenever you want to work on a project, you only have to activate the -corresponding environment. On OS X and Linux, do the following:: - - $ . .venv/bin/activate - (venv) $ - -If you are a Windows user, the following command is for you:: - - > .venv\scripts\activate - (venv) > - -Either way, you should now be using your virtualenv (notice how the prompt of -your shell has changed to show the active environment). - -And if you want to stop using the virtualenv, use the following command:: - - $ deactivate - -After doing this, the prompt of your shell should be as familiar as before. - -Now, let's move on. Enter the following command to get Click activated in your -virtualenv:: - - $ pip install click - -A few seconds later and you are good to go. +Installing into a virtual environment is highly recommended. We suggest :ref:`virtualenv-heading`. Screencast and Examples ----------------------- diff --git a/docs/virtualenv.rst b/docs/virtualenv.rst new file mode 100644 index 000000000..43e0fc5bb --- /dev/null +++ b/docs/virtualenv.rst @@ -0,0 +1,51 @@ +.. _virtualenv-heading: + +Virtualenv +========================= + +Why use virtualenv? +------------------------- + +You should use `Virtualenv `_ because: + +* It allows you to install multiple versions of the same dependency. + +* If you have an operating system version of python, it prevents you from changing its dependencies and potentially messing up your os. + +How to use virtualenv +----------------------------- + +Create your project folder, then a virtualenv within it:: + + $ mkdir myproject + $ cd myproject + $ python3 -m venv .venv + +Now, whenever you want to work on a project, you only have to activate the +corresponding environment. On OS X and Linux, do the following:: + + $ . .venv/bin/activate + (venv) $ + +On Windows, do the following:: + + > .venv\scripts\activate + (venv) > + +You are now using your virtualenv (notice how the prompt of your shell has changed to show the active environment). + +To install in the virtual environment:: + + $ pip install click + +And if you want to stop using the virtualenv, use the following command:: + + $ deactivate + +After doing this, the prompt of your shell should be as familiar as before. + + + + + + From d2e9f1c31e635751614a51ace611e6f769518c04 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 3 Jul 2023 20:27:28 -0700 Subject: [PATCH 02/17] Fix whitespace. --- docs/quickstart.rst | 4 ++-- docs/virtualenv.rst | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1ed00622a..c92416e53 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,13 +3,13 @@ Quickstart .. currentmodule:: click -Install +Install ---------------------- Install from PyPI:: pip install click -Installing into a virtual environment is highly recommended. We suggest :ref:`virtualenv-heading`. +Installing into a virtual environment is highly recommended. We suggest :ref:`virtualenv-heading`. Screencast and Examples ----------------------- diff --git a/docs/virtualenv.rst b/docs/virtualenv.rst index 43e0fc5bb..33feddbac 100644 --- a/docs/virtualenv.rst +++ b/docs/virtualenv.rst @@ -1,18 +1,18 @@ .. _virtualenv-heading: Virtualenv -========================= +========================= Why use virtualenv? -------------------------- +------------------------- -You should use `Virtualenv `_ because: +You should use `Virtualenv `_ because: * It allows you to install multiple versions of the same dependency. * If you have an operating system version of python, it prevents you from changing its dependencies and potentially messing up your os. -How to use virtualenv +How to use virtualenv ----------------------------- Create your project folder, then a virtualenv within it:: @@ -43,9 +43,3 @@ And if you want to stop using the virtualenv, use the following command:: $ deactivate After doing this, the prompt of your shell should be as familiar as before. - - - - - - From 03f5496c3cc61dab03d673228ce8040a096dbf7e Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 4 Jul 2023 11:50:40 -0700 Subject: [PATCH 03/17] Fix typo. --- docs/virtualenv.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/virtualenv.rst b/docs/virtualenv.rst index 33feddbac..e25d77c9e 100644 --- a/docs/virtualenv.rst +++ b/docs/virtualenv.rst @@ -34,7 +34,7 @@ On Windows, do the following:: You are now using your virtualenv (notice how the prompt of your shell has changed to show the active environment). -To install in the virtual environment:: +To install packages in the virtual environment:: $ pip install click From 26662e93496afa5092a70647acd8234a7d16b9d9 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 6 Aug 2023 22:16:44 -0700 Subject: [PATCH 04/17] Make examples section nicer. --- docs/quickstart.rst | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 7cd26ca06..4ef7612f1 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -64,38 +64,26 @@ virtualenv:: A few seconds later and you are good to go. -Screencast and Examples +Examples ----------------------- -There is a screencast available which shows the basic API of Click and -how to build simple applications with it. It also explores how to build -commands with subcommands. +Some stand alone examples of Click applications are packaged with Click. They are available in the `examples folder `_ of the repo. + +* `inout `_ : A very simple example of an application that can read from files and write to files and also accept input from stdin or write to stdout. +* `validation `_ : A simple example of an application that performs custom validation of parameters in different ways. +* `naval `_ : Port of the `docopt `_ naval example. +* `colors `_ : A simple example that colorizes text. Uses colorama on Windows. +* `aliases `_ : An advanced example that implements :ref:`aliases`. +* `imagepipe `_ : A complex example that implements some :ref:`multi-command-chaining` . It chains together image processing instructions. Requires pillow. +* `repo `_ : An advanced example that implements a Git-/Mercurial-like command line interface. +* `complex `_ : A very advanced example that implements loading subcommands dynamically from a plugin folder. +* `termui `_ : A simple example that showcases terminal UI helpers provided by click. + +If you prefer video, an older screencast (2014) is available. It shows shows the basics of Click, how to build simple applications with it, and how to build commands with subcommands. * `Building Command Line Applications with Click `_ -Examples of Click applications can be found in the documentation as well -as in the GitHub repository together with readme files: - -* ``inout``: `File input and output - `_ -* ``naval``: `Port of docopt naval example - `_ -* ``aliases``: `Command alias example - `_ -* ``repo``: `Git-/Mercurial-like command line interface - `_ -* ``complex``: `Complex example with plugin loading - `_ -* ``validation``: `Custom parameter validation example - `_ -* ``colors``: `Color support demo - `_ -* ``termui``: `Terminal UI functions demo - `_ -* ``imagepipe``: `Multi command chaining demo - `_ - Basic Concepts - Creating a Command ----------------------------------- From c438e1a14f325808f3e87b4ebc3ad7fbacac8439 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 12 Aug 2023 00:54:46 -0700 Subject: [PATCH 05/17] Clean up parameters intro. --- docs/arguments.rst | 3 ++ docs/parameters.rst | 112 +++++++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/docs/arguments.rst b/docs/arguments.rst index 60b53c558..5b877d82e 100644 --- a/docs/arguments.rst +++ b/docs/arguments.rst @@ -183,6 +183,9 @@ file in the same folder, and upon completion, the file will be moved over to the original location. This is useful if a file regularly read by other users is modified. + +.. _environment-variables: + Environment Variables --------------------- diff --git a/docs/parameters.rst b/docs/parameters.rst index b3604e750..260a18931 100644 --- a/docs/parameters.rst +++ b/docs/parameters.rst @@ -3,39 +3,60 @@ Parameters .. currentmodule:: click -Click supports two types of parameters for scripts: options and arguments. -There is generally some confusion among authors of command line scripts of -when to use which, so here is a quick overview of the differences. As its -name indicates, an option is optional. While arguments can be optional -within reason, they are much more restricted in how optional they can be. - -To help you decide between options and arguments, the recommendation is -to use arguments exclusively for things like going to subcommands or input -filenames / URLs, and have everything else be an option instead. - -Differences ------------ - -Arguments can do less than options. The following features are only -available for options: - -* automatic prompting for missing input -* act as flags (boolean or otherwise) -* option values can be pulled from environment variables, arguments can not -* options are fully documented in the help page, arguments are not - (:ref:`this is intentional ` as arguments - might be too specific to be automatically documented) - -On the other hand arguments, unlike options, can accept an arbitrary number -of arguments. Options can strictly ever only accept a fixed number of -arguments (defaults to 1), or they may be specified multiple times using -:ref:`multiple-options`. +Click supports only two types of parameters for scripts (by design): options and arguments. + +Options +---------------- + +* are optional. +* Recommended to use for everything except subcommands, urls, or files. +* Can take a fixed number of arguments. The default is 1. They may be specified multiple times using :ref:`multiple-options`. +* Are fully documented by the help page. +* Have automatic prompting for missing input. +* Can act as flags (boolean or otherwise). +* Can be pulled from environment variables. + +Arguments +---------------- + +* are optional with in reason, but not entirely so. +* Recommended to use for subcommands, urls, or files. +* Can take an arbitrary number of arguments. +* Are not fully documented by the help page since they may be too specific to be automatically documented. How to document args is covered in :ref:`documenting args section `. +* Can be pulled from environment variables but only explicitly named ones. See :ref:`environment-variables` for further explanation. + +.. _parameter_names: + +Parameter Names +--------------- + +Parameters (both options and arguments) have a name that will be used as +the Python argument name when calling the decorated function with +values. + +.. click:example:: + + @click.command() + @click.argument('filename') + @click.option('-t', '--times', type=int) + def multi_echo(filename, times): + """Print value of SRC environment variable.""" + for x in range(times): + click.echo(filename) + +In the above example the argument's name is ``filename``. The name must match the python arg name. To provide a different name for use in help text, see :ref:`doc-meta-variables`. +The option's names are ``-t`` and ``--times``. More names are available for options and are covered in :ref:`options`. + +And what it looks like when run: + +.. click:run:: + + invoke(multi_echo, ['--times=3', 'index.txt'], prog_name='multi_echo') Parameter Types --------------- -Parameters can be of different types. Types can be implemented with -different behavior and some are supported out of the box: +The supported parameter types are: ``str`` / :data:`click.STRING`: The default parameter type which indicates unicode strings. @@ -74,37 +95,10 @@ different behavior and some are supported out of the box: .. autoclass:: DateTime :noindex: -Custom parameter types can be implemented by subclassing -:class:`click.ParamType`. For simple cases, passing a Python function that -fails with a `ValueError` is also supported, though discouraged. - -.. _parameter_names: - -Parameter Names ---------------- - -Parameters (both options and arguments) have a name that will be used as -the Python argument name when calling the decorated function with -values. - -Arguments take only one positional name. To provide a different name for -use in help text, see :ref:`doc-meta-variables`. - -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. - - -Implementing Custom Types -------------------------- +How to Implement Custom Types +------------------------------- -To implement a custom type, you need to subclass the :class:`ParamType` -class. Override the :meth:`~ParamType.convert` method to convert the -value from a string to the correct type. +To implement a custom type, you need to subclass the :class:`ParamType` class. For simple cases, passing a Python function that fails with a `ValueError` is also supported, though discouraged. 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 From 8d6604cbb349d5fc0e060f0c50fe4e0df51e3864 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 12 Aug 2023 16:43:57 -0700 Subject: [PATCH 06/17] Fix a few typos. --- docs/documentation.rst | 2 +- docs/parameters.rst | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/documentation.rst b/docs/documentation.rst index 349acfb83..da0aaa148 100644 --- a/docs/documentation.rst +++ b/docs/documentation.rst @@ -36,7 +36,7 @@ And what it looks like: .. _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 diff --git a/docs/parameters.rst b/docs/parameters.rst index 260a18931..ca7dcb6ac 100644 --- a/docs/parameters.rst +++ b/docs/parameters.rst @@ -22,15 +22,15 @@ Arguments * are optional with in reason, but not entirely so. * Recommended to use for subcommands, urls, or files. * Can take an arbitrary number of arguments. -* Are not fully documented by the help page since they may be too specific to be automatically documented. How to document args is covered in :ref:`documenting args section `. -* Can be pulled from environment variables but only explicitly named ones. See :ref:`environment-variables` for further explanation. +* Are not fully documented by the help page since they may be too specific to be automatically documented. For more see :ref:`documenting-arguments`. +* Can be pulled from environment variables but only explicitly named ones. For more see :ref:`environment-variables`. .. _parameter_names: Parameter Names --------------- -Parameters (both options and arguments) have a name that will be used as +Parameters (options and arguments) have a name that will be used as the Python argument name when calling the decorated function with values. @@ -40,7 +40,7 @@ values. @click.argument('filename') @click.option('-t', '--times', type=int) def multi_echo(filename, times): - """Print value of SRC environment variable.""" + """Print value filename multiple times.""" for x in range(times): click.echo(filename) From 483e57fdc5acc8d767c8f4701a19782fd8b6b3f6 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 27 Aug 2023 16:36:50 -0700 Subject: [PATCH 07/17] Fix spelling and remove old links. --- docs/quickstart.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 4ef7612f1..9616b458d 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -67,7 +67,7 @@ A few seconds later and you are good to go. Examples ----------------------- -Some stand alone examples of Click applications are packaged with Click. They are available in the `examples folder `_ of the repo. +Some standalone examples of Click applications are packaged with Click. They are available in the `examples folder `_ of the repo. * `inout `_ : A very simple example of an application that can read from files and write to files and also accept input from stdin or write to stdout. * `validation `_ : A simple example of an application that performs custom validation of parameters in different ways. @@ -79,11 +79,6 @@ Some stand alone examples of Click applications are packaged with Click. They ar * `complex `_ : A very advanced example that implements loading subcommands dynamically from a plugin folder. * `termui `_ : A simple example that showcases terminal UI helpers provided by click. -If you prefer video, an older screencast (2014) is available. It shows shows the basics of Click, how to build simple applications with it, and how to build commands with subcommands. - -* `Building Command Line Applications with Click - `_ - Basic Concepts - Creating a Command ----------------------------------- From 2d5225441aa03cccca3c9c070ebea31ce18af68b Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 27 Aug 2023 16:43:20 -0700 Subject: [PATCH 08/17] Fix capitalization. --- docs/parameters.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/parameters.rst b/docs/parameters.rst index ca7dcb6ac..7291a68c4 100644 --- a/docs/parameters.rst +++ b/docs/parameters.rst @@ -8,7 +8,7 @@ Click supports only two types of parameters for scripts (by design): options an Options ---------------- -* are optional. +* Are optional. * Recommended to use for everything except subcommands, urls, or files. * Can take a fixed number of arguments. The default is 1. They may be specified multiple times using :ref:`multiple-options`. * Are fully documented by the help page. @@ -19,7 +19,7 @@ Options Arguments ---------------- -* are optional with in reason, but not entirely so. +* Are optional with in reason, but not entirely so. * Recommended to use for subcommands, urls, or files. * Can take an arbitrary number of arguments. * Are not fully documented by the help page since they may be too specific to be automatically documented. For more see :ref:`documenting-arguments`. From cb145c3b3135416dd7be7fb8b46b7bdf58164733 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 27 Aug 2023 23:27:56 -0700 Subject: [PATCH 09/17] Add windows / osx tabs. --- docs/virtualenv.rst | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/virtualenv.rst b/docs/virtualenv.rst index e25d77c9e..2a565c102 100644 --- a/docs/virtualenv.rst +++ b/docs/virtualenv.rst @@ -3,16 +3,16 @@ Virtualenv ========================= -Why use virtualenv? +Why Use Virtualenv? ------------------------- You should use `Virtualenv `_ because: * It allows you to install multiple versions of the same dependency. -* If you have an operating system version of python, it prevents you from changing its dependencies and potentially messing up your os. +* If you have an operating system version of Python, it prevents you from changing its dependencies and potentially messing up your os. -How to use virtualenv +How to Use Virtualenv ----------------------------- Create your project folder, then a virtualenv within it:: @@ -22,15 +22,24 @@ Create your project folder, then a virtualenv within it:: $ python3 -m venv .venv Now, whenever you want to work on a project, you only have to activate the -corresponding environment. On OS X and Linux, do the following:: +corresponding environment. - $ . .venv/bin/activate - (venv) $ +.. tabs:: -On Windows, do the following:: + .. group-tab:: OSX/Linux + + .. code-block:: text + + $ . .venv/bin/activate + (venv) $ + + .. group-tab:: Windows + + .. code-block:: text + + > .venv\scripts\activate + (venv) > - > .venv\scripts\activate - (venv) > You are now using your virtualenv (notice how the prompt of your shell has changed to show the active environment). From afc86c748c214939e2293ec00879d85293bcd9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kriszti=C3=A1n=20L=C3=B6ki?= <33157532+krisztianloki@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:43:32 +0200 Subject: [PATCH 10/17] Fix colored output on Windows (#2607) Co-authored-by: Andreas Backx --- CHANGES.rst | 2 ++ src/click/utils.py | 2 +- tests/test_utils.py | 18 +++++++++++------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7ca76f967..8ce12a8ce 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,8 @@ Unreleased :issue:`2705` - Show correct value for flag default when using ``default_map``. :issue:`2632` +- Fix ``click.echo(color=...)`` passing ``color`` to coloroma so it can be + forced on Windows. :issue:`2606`. Version 8.1.7 diff --git a/src/click/utils.py b/src/click/utils.py index 875f9fd4f..836c6f21a 100644 --- a/src/click/utils.py +++ b/src/click/utils.py @@ -311,7 +311,7 @@ def echo( out = strip_ansi(out) elif WIN: if auto_wrap_for_ansi is not None: - file = auto_wrap_for_ansi(file) # type: ignore + file = auto_wrap_for_ansi(file, color) # type: ignore elif not color: out = strip_ansi(out) diff --git a/tests/test_utils.py b/tests/test_utils.py index fd099e9b2..a1ed559b2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -36,9 +36,7 @@ def cli(): def test_echo_custom_file(): - import io - - f = io.StringIO() + f = StringIO() click.echo("hello", file=f) assert f.getvalue() == "hello\n" @@ -209,7 +207,6 @@ 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.") def test_echo_color_flag(monkeypatch, capfd): isatty = True monkeypatch.setattr(click._compat, "isatty", lambda x: isatty) @@ -232,9 +229,16 @@ def test_echo_color_flag(monkeypatch, capfd): assert out == f"{styled_text}\n" isatty = False - click.echo(styled_text) - out, err = capfd.readouterr() - assert out == f"{text}\n" + # Faking isatty() is not enough on Windows; + # the implementation caches the colorama wrapped stream + # so we have to use a new stream for each test + stream = StringIO() + click.echo(styled_text, file=stream) + assert stream.getvalue() == f"{text}\n" + + stream = StringIO() + click.echo(styled_text, file=stream, color=True) + assert stream.getvalue() == f"{styled_text}\n" def test_prompt_cast_default(capfd, monkeypatch): From 8c842a43e884c5e69032223563649f4389e37dd8 Mon Sep 17 00:00:00 2001 From: Dan Ziring <9089037+dzcode@users.noreply.github.com> Date: Sat, 26 Oct 2024 08:12:23 -0700 Subject: [PATCH 11/17] Pass 'color' explicitly in error echoing (#2273) Co-authored-by: Andreas Backx --- src/click/exceptions.py | 10 +++++++++- tests/test_testing.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 64e7f0ec5..0b8315166 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -3,6 +3,7 @@ from gettext import ngettext from ._compat import get_text_stderr +from .globals import resolve_color_default from .utils import echo from .utils import format_filename @@ -29,6 +30,9 @@ class ClickException(Exception): def __init__(self, message: str) -> None: super().__init__(message) + # The context will be removed by the time we print the message, so cache + # the color settings here to be used later on (in `show`) + self.show_color: t.Optional[bool] = resolve_color_default() self.message = message def format_message(self) -> str: @@ -41,7 +45,11 @@ def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: if file is None: file = get_text_stderr() - echo(_("Error: {message}").format(message=self.format_message()), file=file) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=self.show_color, + ) class UsageError(ClickException): diff --git a/tests/test_testing.py b/tests/test_testing.py index 73010f624..0d227f2a0 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -5,6 +5,7 @@ import pytest import click +from click.exceptions import ClickException from click.testing import CliRunner @@ -199,6 +200,26 @@ def cli(): assert not result.exception +def test_with_color_errors(): + class CLIError(ClickException): + def format_message(self) -> str: + return click.style(self.message, fg="red") + + @click.command() + def cli(): + raise CLIError("Red error") + + runner = CliRunner() + + result = runner.invoke(cli) + assert result.output == "Error: Red error\n" + assert result.exception + + result = runner.invoke(cli, color=True) + assert result.output == f"Error: {click.style('Red error', fg='red')}\n" + assert result.exception + + def test_with_color_but_pause_not_blocking(): @click.command() def cli(): From 8fa833e33599fcb7a8ead3f7d9ce80e5037d906c Mon Sep 17 00:00:00 2001 From: jaseg <136313+jaseg@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:38:39 +0200 Subject: [PATCH 12/17] core.py: Make error messages more verbose (#2453) Co-authored-by: Andreas Backx --- src/click/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/click/core.py b/src/click/core.py index 8d9d99558..29e46bd70 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -2671,7 +2671,9 @@ def _parse_decls( if name is None: if not expose_value: return None, opts, secondary_opts - raise TypeError("Could not determine name for option") + raise TypeError( + f"Could not determine name for option with declarations {decls!r}" + ) if not opts and not secondary_opts: raise TypeError( @@ -3011,7 +3013,7 @@ def _parse_decls( if not decls: if not expose_value: return None, [], [] - raise TypeError("Could not determine name for argument") + raise TypeError("Argument is marked as exposed, but does not have a name.") if len(decls) == 1: name = arg = decls[0] name = name.replace("-", "_").lower() From 26aa7bf3189e19a8a63890b1c2cc7eca24312d6f Mon Sep 17 00:00:00 2001 From: Kevin Deldycke Date: Sat, 2 Nov 2024 18:44:23 +0400 Subject: [PATCH 13/17] Deduplicate `--help` option definition (#2563) Co-authored-by: Andreas Backx --- src/click/__init__.py | 1 + src/click/core.py | 22 +++++++----------- src/click/decorators.py | 51 ++++++++++++++++++++++++----------------- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/click/__init__.py b/src/click/__init__.py index cfef88ea8..46f99e951 100644 --- a/src/click/__init__.py +++ b/src/click/__init__.py @@ -19,6 +19,7 @@ from .decorators import confirmation_option as confirmation_option from .decorators import group as group from .decorators import help_option as help_option +from .decorators import HelpOption as HelpOption from .decorators import make_pass_decorator as make_pass_decorator from .decorators import option as option from .decorators import pass_context as pass_context diff --git a/src/click/core.py b/src/click/core.py index 29e46bd70..c623764ee 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1285,25 +1285,19 @@ def get_help_option_names(self, ctx: Context) -> t.List[str]: return list(all_names) def get_help_option(self, ctx: Context) -> t.Optional["Option"]: - """Returns the help option object.""" + """Returns the help option object. + + Unless ``add_help_option`` is ``False``. + """ help_options = self.get_help_option_names(ctx) if not help_options or not self.add_help_option: return None - def show_help(ctx: Context, param: "Parameter", value: str) -> None: - 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."), - ) + # Avoid circular import. + from .decorators import HelpOption + + return HelpOption(help_options) def make_parser(self, ctx: Context) -> OptionParser: """Creates the underlying option parser for this command.""" diff --git a/src/click/decorators.py b/src/click/decorators.py index c346c702a..bcf8906e7 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -522,32 +522,41 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None: return option(*param_decls, **kwargs) -def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: - """Add a ``--help`` option which immediately prints the help page +class HelpOption(Option): + """Pre-configured ``--help`` option which immediately prints the help page and exits the program. + """ - This is usually unnecessary, as the ``--help`` option is added to - each command automatically unless ``add_help_option=False`` is - passed. + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + **kwargs: t.Any, + ) -> None: + if not param_decls: + param_decls = ("--help",) - :param param_decls: One or more option names. Defaults to the single - value ``"--help"``. - :param kwargs: Extra arguments are passed to :func:`option`. - """ + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs.setdefault("callback", self.show_help) - def callback(ctx: Context, param: Parameter, value: bool) -> None: - if not value or ctx.resilient_parsing: - return + super().__init__(param_decls, **kwargs) - echo(ctx.get_help(), color=ctx.color) - ctx.exit() + @staticmethod + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ```` and exits.""" + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() - if not param_decls: - param_decls = ("--help",) - kwargs.setdefault("is_flag", True) - kwargs.setdefault("expose_value", False) - kwargs.setdefault("is_eager", True) - kwargs.setdefault("help", _("Show this message and exit.")) - kwargs["callback"] = callback +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Decorator for the pre-configured ``--help`` option defined above. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + kwargs.setdefault("cls", HelpOption) return option(*param_decls, **kwargs) From 2357253dd1f6236715639c957f0ab33b804fb0f2 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 2 Nov 2024 18:48:56 -0700 Subject: [PATCH 14/17] Resolve typing errors. --- src/click/decorators.py | 2 +- src/click/exceptions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/click/decorators.py b/src/click/decorators.py index 46c5a6099..e2c8fba05 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -531,7 +531,7 @@ class HelpOption(Option): def __init__( self, - param_decls: t.Optional[t.Sequence[str]] = None, + param_decls: t.Optional | t.Sequence[str] = None, **kwargs: t.Any, ) -> None: if not param_decls: diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 702515cf3..30f064449 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -33,7 +33,7 @@ def __init__(self, message: str) -> None: super().__init__(message) # The context will be removed by the time we print the message, so cache # the color settings here to be used later on (in `show`) - self.show_color: t.Optional[bool] = resolve_color_default() + self.show_color: t.Optional | bool = resolve_color_default() self.message = message def format_message(self) -> str: From 27abb990b1582c6f95b91fa965d74c6d2db9c9f6 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Nov 2024 00:23:52 -0700 Subject: [PATCH 15/17] Fix broken documentation reference. --- docs/commands.rst | 3 ++- docs/quickstart.rst | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/commands.rst b/docs/commands.rst index 0cdc169f9..5fe24067f 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -275,9 +275,10 @@ When using chaining, there are a few restrictions: - The :attr:`Context.invoked_subcommand` attribute will be ``'*'`` because the parser doesn't know the full list of commands that will run yet. +.. _command-pipelines: Command Pipelines ------------------ +------------------ When using chaining, a common pattern is to have each command process the result of the previous command. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index d7999986c..310e2b251 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -21,7 +21,7 @@ Some standalone examples of Click applications are packaged with Click. They are * `naval `_ : Port of the `docopt `_ naval example. * `colors `_ : A simple example that colorizes text. Uses colorama on Windows. * `aliases `_ : An advanced example that implements :ref:`aliases`. -* `imagepipe `_ : A complex example that implements some :ref:`multi-command-chaining` . It chains together image processing instructions. Requires pillow. +* `imagepipe `_ : A complex example that implements some :ref:`command-pipelines` . It chains together image processing instructions. Requires pillow. * `repo `_ : An advanced example that implements a Git-/Mercurial-like command line interface. * `complex `_ : A very advanced example that implements loading subcommands dynamically from a plugin folder. * `termui `_ : A simple example that showcases terminal UI helpers provided by click. From 7ee6b6612641bd40064b0aa1c01450916b018416 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Nov 2024 00:43:22 -0700 Subject: [PATCH 16/17] Revert incorrect typing changes. --- src/click/decorators.py | 2 +- src/click/exceptions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/click/decorators.py b/src/click/decorators.py index e2c8fba05..46c5a6099 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -531,7 +531,7 @@ class HelpOption(Option): def __init__( self, - param_decls: t.Optional | t.Sequence[str] = None, + param_decls: t.Optional[t.Sequence[str]] = None, **kwargs: t.Any, ) -> None: if not param_decls: diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 30f064449..702515cf3 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -33,7 +33,7 @@ def __init__(self, message: str) -> None: super().__init__(message) # The context will be removed by the time we print the message, so cache # the color settings here to be used later on (in `show`) - self.show_color: t.Optional | bool = resolve_color_default() + self.show_color: t.Optional[bool] = resolve_color_default() self.message = message def format_message(self) -> str: From dea4ff7763e0d6233ab3689a038344d122de9d3a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Nov 2024 16:04:57 -0800 Subject: [PATCH 17/17] Fix typing issues. --- src/click/decorators.py | 3 ++- src/click/exceptions.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/click/decorators.py b/src/click/decorators.py index 46c5a6099..901f831ad 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -2,6 +2,7 @@ import inspect import typing as t +from collections import abc from functools import update_wrapper from gettext import gettext as _ @@ -531,7 +532,7 @@ class HelpOption(Option): def __init__( self, - param_decls: t.Optional[t.Sequence[str]] = None, + param_decls: abc.Sequence[str] | None = None, **kwargs: t.Any, ) -> None: if not param_decls: diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 702515cf3..3bae1e762 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -33,7 +33,7 @@ def __init__(self, message: str) -> None: super().__init__(message) # The context will be removed by the time we print the message, so cache # the color settings here to be used later on (in `show`) - self.show_color: t.Optional[bool] = resolve_color_default() + self.show_color: bool | None = resolve_color_default() self.message = message def format_message(self) -> str: