Skip to content
This repository was archived by the owner on Jul 21, 2025. It is now read-only.

Commit 7053794

Browse files
committed
Uploaded example.
1 parent d417c71 commit 7053794

File tree

11 files changed

+758
-1
lines changed

11 files changed

+758
-1
lines changed

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,42 @@
1-
# python-click-autocomplete-example
1+
# python-click-autocomplete-example
2+
3+
This is an example on how to setup shell autocompletion in your python scripts reading input via [click](https://github.com/pallets/click/), especially when working on a Python package using [Poetry](https://github.com/python-poetry/poetry).
4+
5+
## Requirements
6+
7+
To test this example you will need:
8+
9+
* **Python v3.8+**
10+
* **Poetry**
11+
12+
## Usage
13+
14+
Set up the environment with:
15+
16+
```bash
17+
git clone https://github.com/ggirelli/python-click-autocomplete-example.git
18+
cd python-click-autocomplete-example
19+
poetry install
20+
poetry shell
21+
```
22+
23+
This example package provides you with two scripts:
24+
25+
* `clicko` is a placeholder mimicking a normal script with subcommands and commands.
26+
* `clicko-autocomplete` can be used to enable autocompletion for the `clicko` script.
27+
28+
Currently, autocompletion is supported for `bash`, `fish`, and `zsh`. To activate it, simply run:
29+
30+
```bash
31+
clicko-autocomplete -s SHELL_TYPE
32+
```
33+
34+
The default `SHELL_TYPE` is `bash`.
35+
36+
Now, you should be able to test autocompletion by typing `clicko <TAB> <TAB>`.
37+
38+
## How does it work
39+
40+
The `clicko-autocomplete` script is able to generate and install shell autocompletion configuration files, stored by default in the `autocomplete` subfolder of this package. This is done by exploiting the in-built autocompletion tools served by `click`.
41+
42+
To read more on autocompletion with `click`, check out [their documentation](https://click.palletsprojects.com/en/8.0.x/shell-completion/).

clickomplete/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
@author: Gabriele Girelli
3+
@contact: gigi.ga90@gmail.com
4+
"""
5+
6+
from importlib.metadata import version
7+
from typing import List
8+
9+
try:
10+
__version__ = version(__name__)
11+
except Exception as e:
12+
raise e
13+
14+
__all__ = ["__version__"]
15+
__path__: List[str]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
_clicko_completion() {
2+
local IFS=$'\n'
3+
local response
4+
5+
response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD _CLICKO_COMPLETE=bash_complete $1)
6+
7+
for completion in $response; do
8+
IFS=',' read type value <<< "$completion"
9+
10+
if [[ $type == 'dir' ]]; then
11+
COMREPLY=()
12+
compopt -o dirnames
13+
elif [[ $type == 'file' ]]; then
14+
COMREPLY=()
15+
compopt -o default
16+
elif [[ $type == 'plain' ]]; then
17+
COMPREPLY+=($value)
18+
fi
19+
done
20+
21+
return 0
22+
}
23+
24+
_clicko_completion_setup() {
25+
complete -o nosort -F _clicko_completion clicko
26+
}
27+
28+
_clicko_completion_setup;
29+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function _clicko_completion;
2+
set -l response;
3+
4+
for value in (env _CLICKO_COMPLETE=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) clicko);
5+
set response $response $value;
6+
end;
7+
8+
for completion in $response;
9+
set -l metadata (string split "," $completion);
10+
11+
if test $metadata[1] = "dir";
12+
__fish_complete_directories $metadata[2];
13+
else if test $metadata[1] = "file";
14+
__fish_complete_path $metadata[2];
15+
else if test $metadata[1] = "plain";
16+
echo $metadata[2];
17+
end;
18+
end;
19+
end;
20+
21+
complete --no-files --command clicko --arguments "(_clicko_completion)";
22+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#compdef clicko
2+
3+
_clicko_completion() {
4+
local -a completions
5+
local -a completions_with_descriptions
6+
local -a response
7+
(( ! $+commands[clicko] )) && return 1
8+
9+
response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) _CLICKO_COMPLETE=zsh_complete clicko)}")
10+
11+
for type key descr in ${response}; do
12+
if [[ "$type" == "plain" ]]; then
13+
if [[ "$descr" == "_" ]]; then
14+
completions+=("$key")
15+
else
16+
completions_with_descriptions+=("$key":"$descr")
17+
fi
18+
elif [[ "$type" == "dir" ]]; then
19+
_path_files -/
20+
elif [[ "$type" == "file" ]]; then
21+
_path_files -f
22+
fi
23+
done
24+
25+
if [ -n "$completions_with_descriptions" ]; then
26+
_describe -V unsorted completions_with_descriptions -U
27+
fi
28+
29+
if [ -n "$completions" ]; then
30+
compadd -U -V unsorted -a completions
31+
fi
32+
}
33+
34+
compdef _clicko_completion clicko;
35+

clickomplete/const.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""
2+
@author: Gabriele Girelli
3+
@contact: gigi.ga90@gmail.com
4+
"""
5+
6+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])

clickomplete/scripts/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
@author: Gabriele Girelli
3+
@contact: gigi.ga90@gmail.com
4+
"""
5+
6+
from clickomplete.scripts import autocomplete, clicko
7+
8+
import logging
9+
from rich.logging import RichHandler # type: ignore
10+
11+
logging.basicConfig(
12+
level=logging.INFO,
13+
format="%(message)s",
14+
handlers=[RichHandler(markup=True, rich_tracebacks=True)],
15+
)
16+
17+
__all__ = [
18+
"autocomplete",
19+
"clicko",
20+
]
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""
2+
@author: Gabriele Girelli
3+
@contact: gigi.ga90@gmail.com
4+
"""
5+
6+
import click # type: ignore
7+
import os
8+
from rich import print # type: ignore
9+
from shutil import copyfile
10+
import sys
11+
12+
from clickomplete import __path__, __version__
13+
from clickomplete.const import CONTEXT_SETTINGS
14+
15+
16+
@click.command(context_settings=CONTEXT_SETTINGS)
17+
@click.option(
18+
"--shell-type",
19+
"-s",
20+
help="shell type for which to activate autocompletion",
21+
type=click.Choice(["bash", "zsh", "fish"], case_sensitive=False),
22+
default="bash",
23+
show_default=True,
24+
)
25+
@click.option(
26+
"--regenerate",
27+
help="to regenerate autocompletion file, mainly for developers",
28+
type=click.BOOL,
29+
default=False,
30+
is_flag=True,
31+
)
32+
@click.version_option(__version__)
33+
def main(shell_type: str, regenerate: bool) -> None:
34+
user_home_path = os.path.expanduser("~")
35+
autocomplete_path = os.path.join(
36+
__path__[0], "autocomplete", f".clicko-complete.{shell_type}"
37+
)
38+
39+
if regenerate:
40+
regenerate_autocompletion_files(shell_type, autocomplete_path)
41+
42+
if shell_type in ("bash", "zsh"):
43+
autocomplete_bash_or_zsh(user_home_path, autocomplete_path, shell_type)
44+
elif "fish" == shell_type:
45+
autocomplete_fish(user_home_path, autocomplete_path)
46+
47+
print("Done. :thumbs_up: :smiley:")
48+
49+
50+
def regenerate_autocompletion_files(shell_type: str, autocomplete_path: str) -> None:
51+
os.system(f"_CLICKO_COMPLETE={shell_type}_source clicko > {autocomplete_path}")
52+
print(f"Regenerated {shell_type} completion file: {autocomplete_path}")
53+
54+
55+
def autocomplete_fish(user_home_path: str, autocomplete_path: str) -> None:
56+
destination_path = os.path.join(
57+
user_home_path, ".config/fish/completions/clicko.fish"
58+
)
59+
60+
if os.path.isfile(destination_path):
61+
print("Autocompletion was previously set up. Skipping.")
62+
sys.exit()
63+
64+
copyfile(
65+
autocomplete_path,
66+
destination_path,
67+
)
68+
69+
70+
def autocomplete_bash_or_zsh(
71+
user_home_path: str, autocomplete_path: str, shell_type: str = "bash"
72+
) -> None:
73+
assert shell_type in ("bash", "zsh")
74+
75+
autocompletion_string = f". {autocomplete_path} # CLICKO-AUTOCOMPLETE\n"
76+
run_command_path = os.path.join(user_home_path, f".{shell_type}rc")
77+
78+
with open(run_command_path, "r") as OH:
79+
if autocompletion_string in OH.readlines():
80+
print("Autocompletion was previously set up. Skipping.")
81+
sys.exit()
82+
83+
with open(run_command_path, "a+") as OH:
84+
OH.write(f"\n{autocompletion_string}")

clickomplete/scripts/clicko.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
@author: Gabriele Girelli
3+
@contact: gigi.ga90@gmail.com
4+
"""
5+
6+
import click # type: ignore
7+
from clickomplete import __version__
8+
from clickomplete.const import CONTEXT_SETTINGS
9+
import webbrowser
10+
import sys
11+
12+
13+
@click.group(
14+
name="radiant",
15+
context_settings=CONTEXT_SETTINGS,
16+
help=f"""\b
17+
Version: {__version__}
18+
Author: Gabriele Girelli
19+
Docs: http://github.com/python-click-autocomplete-example
20+
Code: http://github.com/ggirelli/python-click-autocomplete-example
21+
22+
Radial Image Analisys Toolkit (RadIAnTkit) is a Python3.6+ package containing
23+
tools for radial analysis of microscopy image.""",
24+
)
25+
@click.version_option(__version__)
26+
def main() -> None:
27+
pass
28+
29+
30+
@click.command(
31+
"_docs",
32+
help="Open online documentation on your favorite browser.",
33+
)
34+
def open_documentation() -> None:
35+
webbrowser.open("https://github.com/python-click-autocomplete-example/")
36+
sys.exit()
37+
38+
39+
@click.command(
40+
"command_1",
41+
help="Does something.",
42+
)
43+
def command_1():
44+
pass
45+
46+
47+
@click.command(
48+
"command_2",
49+
help="Does something else.",
50+
)
51+
def command_2():
52+
pass
53+
54+
55+
main.add_command(open_documentation)
56+
main.add_command(command_1)
57+
main.add_command(command_2)
58+
59+
60+
@click.group(
61+
name="subgroup_1",
62+
context_settings=CONTEXT_SETTINGS,
63+
help="Groups some sub-commands.",
64+
)
65+
def subgroup_1():
66+
pass
67+
68+
69+
@click.command(
70+
"command_1",
71+
help="Does something new.",
72+
)
73+
def subgroup_1_command_1():
74+
pass
75+
76+
77+
subgroup_1.add_command(subgroup_1_command_1)
78+
main.add_command(subgroup_1)

0 commit comments

Comments
 (0)