Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## Version 9.0.0

Unreleased

- Breaking: an automatically derived parameter name must be a valid Python
identifier, or `TypeError` is raised. {class}`Argument` and {class}`Option`
derive their name the same way and apply the same check. {pr}`3827`
- Breaking: `expose_value=False` no longer bypass the name check for a parameter.
{pr}`3827`
- Breaking: an {class}`Option` now normalizes its declaration written as a Python
identifier to determine the parameter name. {pr}`3827`
- Breaking: neither kind builds a parameter without a declaration.
`click.argument(expose_value=False)` and `click.option(expose_value=False)`
used to name a parameter `""`. {pr}`3827`

## Version 8.5.1

Unreleased
Expand Down
26 changes: 26 additions & 0 deletions docs/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,32 @@ recognized, otherwise {data}`STRING` is used. If no default value is
provided, the type is assumed to be {data}`STRING`. See
{ref}`type-inference` for the types that are recognized.

(argument-names)=

## Argument Names

The single declaration is not used as the name verbatim. Every `-` is replaced
with `_` and the result is lower cased, so `click.argument("input-file")` names
its parameter `input_file`. That is the same transform options apply, and it is
likewise not reversible.

The name must satisfy {meth}`str.isidentifier`, so that the callback can
receive it as a keyword argument. {ref}`Options <option-names>` derive their
name the same way and apply the same check. The
{ref}`caution about reserved keywords <keyword-names>` applies here too.

An argument takes exactly one declaration, and passing more raises
{exc}`TypeError`. That declaration becomes the name through the
{ref}`transform every parameter shares <name-transform>`, where the examples
live.

`expose_value=False` is no exception, because the name is also the key the
parser stores the value under.

An argument takes exactly one declaration, where an option takes several and
{ref}`picks one of them <option-names>`. Past that choice both kinds derive the
name the same way.

```{admonition} Note on Required Arguments
:class: note

Expand Down
64 changes: 46 additions & 18 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,41 +60,67 @@ and converting underscores to dashes.
invoke(echo, args=['--string-to-echo', 'Hi!'])
```

More formally, Click will try to infer the decorated function argument name as
follows:
(option-names)=

## Option Names

An option carries several declarations, so Click picks one of them to name the
parameter:

1. If a positional argument is a valid [Python identifier](https://docs.python.org/3/reference/lexical_analysis.html#identifiers) (and thus does not have dashes), it is chosen.
2. If multiple positional arguments are prefixed with `--`, the first one
declared is chosen.
3. Otherwise, the first positional argument prefixed with `-` is chosen.

To get the argument name, the chosen positional argument is converted to lower
case, a leading `-` or `--` is removed if found, and any remaining `-`
characters are replaced with `_`.
The chosen declaration is not used as the name verbatim. Its `-` or `--` prefix
is dropped, every remaining `-` is replaced with `_` and the result is lower
cased, so `click.option("--input-file")` names its parameter `input_file`. That
holds for rule 1 too: an identifier declaration says which declaration names the
parameter, not what the name is spelled like.

The name must satisfy {meth}`str.isidentifier`, so that the callback can
receive it as a keyword argument. {ref}`Arguments <argument-names>` derive their
name the same way and apply the same check, including the
{ref}`caution about reserved keywords <keyword-names>`.

```{eval-rst}
.. list-table:: Examples
:widths: 15 15
:widths: 25 15
:header-rows: 1

* - Decorator Arguments
- Inferred Argument Name
- Declaration Chosen
* - ``"-f", "--foo-bar"``
- foo_bar
* - ``"-x"``
- x
* - ``"-f", "--filename", "dest"``
- dest
* - ``"--CamelCase"``
- camelcase
* - ``"-f", "-fb"``
- f
- ``--foo-bar``
* - ``"--f", "--foo-bar"``
- f
- ``--f``
* - ``"-f", "-fb"``
- ``-f``
* - ``"-f", "--filename", "dest"``
- ``dest``
* - ``"-f", "--filename", "Dest"``
- ``Dest``
* - ``"---f"``
- _f
- ``---f``
```

The chosen declaration then becomes the name through the
{ref}`transform every parameter shares <name-transform>`: the `-` or `--`
prefix is dropped, every remaining `-` becomes a `_`, and the result is lower
cased and checked. So `"-f", "--filename", "Dest"` names `dest`.

Only the leading one or two dashes are ever a prefix. Every other dash becomes
an underscore wherever it sits, so `"---f"` names `_f` and `"---a----b--"`
names `_a____b__`.

That transform is many-to-one, which is deliberate here: it lets several
options share a name to form a
[feature switch group](#feature-switch-group).

`expose_value=False` is no exception, because the name is also the parser dest
the value is stored under. Pass an explicit name instead:
`click.option("--0-file", "zero_file", expose_value=False)`.

## Basic Example

A simple {class}`click.Option` takes one option name. By default, it's assumed
Expand Down Expand Up @@ -509,6 +535,8 @@ literally.
¹: `default=True` is substituted with `flag_value`.
```

(feature-switch-group)=

#### Feature switch groups (multiple flags sharing one variable)

Several `flag_value` options can target the same parameter name to form a
Expand Down
95 changes: 94 additions & 1 deletion docs/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ the Python argument name when calling the decorated function with
values.

In the 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`.
The option's names are `-t` and `--times`. More names are available for options and are covered in {ref}`option names <option-names>`.

```{eval-rst}
.. click:example::
Expand All @@ -53,3 +53,96 @@ The option's names are `-t` and `--times`. More names are available for options

invoke(multi_echo, ['--times=3', 'index.txt'], prog_name='multi_echo')
```

(name-transform)=

Both kinds derive that name the same way. An option drops its prefix first, and
only the leading one or two dashes are ever a prefix. From there every `-`
becomes a `_`, wherever it sits, and the result is lower cased.
It must then satisfy {meth}`str.isidentifier`, so that the callback can receive
it as a keyword argument, and {exc}`TypeError` is raised when it does not.

```{eval-rst}
.. list-table:: Examples
:widths: 20 20 15
:header-rows: 1

* - Argument Declaration
- Option Declaration
- Inferred Name
* - ``"foo-bar"``
- ``"--foo-bar"``
- foo_bar
* - ``"Foo-Bar"``
- ``"--Foo-Bar"``
- foo_bar
* - ``"Foo_Bar"``
- ``"--Foo_Bar"``
- foo_bar
* - ``"x"``
- ``"-x"``
- x
* - ``"CamelCase"``
- ``"--CamelCase"``
- camelcase
* - ``"café"``
- ``"--café"``
- café
* - ``"ΟΔΟΣ"``
- ``"--ΟΔΟΣ"``
- οδος
* - ``"\N{KELVIN SIGN}"``
- ``"--\N{KELVIN SIGN}"``
- k
* - ``"foo-٣"``
- ``"--foo-٣"``
- foo_٣
* - ``"a-----b"``
- ``"--a-----b"``
- a_____b
* - ``"a--"``
- ``"--a--"``
- a__
* - ``"-a----b--"``
- ``"---a----b--"``
- _a____b__
* - ``"--"``
- ``"----"``
- __
* - ``"0-file"``
- ``"--0-file"``
- :exc:`TypeError`
* - ``"٣foo"``
- ``"--٣foo"``
- :exc:`TypeError`
* - ``"foo.bar"``
- ``"--foo.bar"``
- :exc:`TypeError`
* - ``"foo\N{NON-BREAKING HYPHEN}bar"``
- ``"--foo\N{NON-BREAKING HYPHEN}bar"``
- :exc:`TypeError`
* - ``"a\N{ZERO WIDTH SPACE}b"``
- ``"--a\N{ZERO WIDTH SPACE}b"``
- :exc:`TypeError`
* - ``""``
- ``"--"``
- :exc:`TypeError`
```

The transform is many-to-one and not reversible: the three spellings of
`foo-bar` above all name one parameter. Which declaration is transformed in the
first place is the only thing that differs between the two kinds, covered in
{ref}`option names <option-names>` and {ref}`argument names <argument-names>`.

(keyword-names)=

```{caution}
A [reserved keyword](https://docs.python.org/3/reference/lexical_analysis.html#keywords)
satisfies {meth}`str.isidentifier`, so Click accepts one: `click.option("--from")`
names its parameter `from`. No callback can declare that, so the command has to
accept `**kwargs`, and Python then stops checking the callback signature at all.

Pass an explicit name instead: `click.option("--from", "source")`. An argument
takes one declaration and has no explicit-name channel, so rename the
declaration there.
```
Loading