Skip to content

Commit a2623d0

Browse files
authored
Move Extending-click page from reST to MyST (#3106)
2 parents 6707116 + 2d1c49d commit a2623d0

2 files changed

Lines changed: 132 additions & 138 deletions

File tree

docs/extending-click.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Extending Click
2+
3+
```{currentmodule} click
4+
```
5+
6+
In addition to common functionality that is implemented in the library itself, there are countless patterns that can be
7+
implemented by extending Click. This page should give some insight into what can be accomplished.
8+
9+
```{contents}
10+
:depth: 2
11+
:local: true
12+
```
13+
14+
(custom-groups)=
15+
16+
## Custom Groups
17+
18+
You can customize the behavior of a group beyond the arguments it accepts by subclassing {class}`click.Group`.
19+
20+
The most common methods to override are {meth}`~click.Group.get_command` and {meth}`~click.Group.list_commands`.
21+
22+
The following example implements a basic plugin system that loads commands from Python files in a folder. The command is
23+
lazily loaded to avoid slow startup.
24+
25+
```python
26+
import importlib.util
27+
import os
28+
import click
29+
30+
class PluginGroup(click.Group):
31+
def __init__(self, name=None, plugin_folder="commands", **kwargs):
32+
super().__init__(name=name, **kwargs)
33+
self.plugin_folder = plugin_folder
34+
35+
def list_commands(self, ctx):
36+
rv = []
37+
38+
for filename in os.listdir(self.plugin_folder):
39+
if filename.endswith(".py"):
40+
rv.append(filename[:-3])
41+
42+
rv.sort()
43+
return rv
44+
45+
def get_command(self, ctx, name):
46+
path = os.path.join(self.plugin_folder, f"{name}.py")
47+
spec = importlib.util.spec_from_file_location(name, path)
48+
module = importlib.util.module_from_spec(spec)
49+
spec.loader.exec_module(module)
50+
return module.cli
51+
52+
cli = PluginGroup(
53+
plugin_folder=os.path.join(os.path.dirname(__file__), "commands")
54+
)
55+
56+
if __name__ == "__main__":
57+
cli()
58+
```
59+
60+
Custom classes can also be used with decorators:
61+
62+
```python
63+
@click.group(
64+
cls=PluginGroup,
65+
plugin_folder=os.path.join(os.path.dirname(__file__), "commands")
66+
)
67+
def cli():
68+
pass
69+
```
70+
71+
(aliases)=
72+
73+
## Command Aliases
74+
75+
Many tools support aliases for commands. For example, you can configure `git` to accept `git ci` as alias for
76+
`git commit`. Other tools also support auto-discovery for aliases by automatically shortening them.
77+
78+
It's possible to customize {class}`Group` to provide this functionality. As explained in {ref}`custom-groups`, a group
79+
provides two methods: {meth}`~Group.list_commands` and {meth}`~Group.get_command`. In this particular case, you only
80+
need to override the latter as you generally don't want to enumerate the aliases on the help page in order to avoid
81+
confusion.
82+
83+
The following example implements a subclass of {class}`Group` that accepts a prefix for a command. If there was a
84+
command called `push`, it would accept `pus` as an alias (so long as it was unique):
85+
86+
```{eval-rst}
87+
.. click:example::
88+
89+
class AliasedGroup(click.Group):
90+
def get_command(self, ctx, cmd_name):
91+
rv = super().get_command(ctx, cmd_name)
92+
93+
if rv is not None:
94+
return rv
95+
96+
matches = [
97+
x for x in self.list_commands(ctx)
98+
if x.startswith(cmd_name)
99+
]
100+
101+
if not matches:
102+
return None
103+
104+
if len(matches) == 1:
105+
return click.Group.get_command(self, ctx, matches[0])
106+
107+
ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")
108+
109+
def resolve_command(self, ctx, args):
110+
# always return the full command name
111+
_, cmd, args = super().resolve_command(ctx, args)
112+
return cmd.name, cmd, args
113+
```
114+
115+
It can be used like this:
116+
117+
```python
118+
119+
@click.group(cls=AliasedGroup)
120+
def cli():
121+
pass
122+
123+
@cli.command
124+
def push():
125+
pass
126+
127+
@cli.command
128+
def pop():
129+
pass
130+
```
131+
132+
See the [alias example](https://github.com/pallets/click/tree/main/examples/aliases) in Click's repository for another example.

docs/extending-click.rst

Lines changed: 0 additions & 138 deletions
This file was deleted.

0 commit comments

Comments
 (0)