Skip to content

Commit 1ef02c0

Browse files
fix: Use Click v8.x native shell completion
* Use click v8.x's native shell completions to produce pyhf shell completions for the CLI API. - c.f. https://click.palletsprojects.com/en/stable/shell-completion/ * Remove shellcomplete extra. * Update tests for CLI API. * Extend documentation for producing shell completions for Bash, Zsh, and Fish shells.
1 parent cad5592 commit 1ef02c0

File tree

3 files changed

+92
-38
lines changed

3 files changed

+92
-38
lines changed

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ Homepage = "https://github.com/scikit-hep/pyhf"
6464
"Source Code" = "https://github.com/scikit-hep/pyhf"
6565

6666
[project.optional-dependencies]
67-
shellcomplete = ["click_completion"]
6867
jax = [
6968
"jax>=0.4.1", # c.f. PR #2079
7069
"jaxlib>=0.4.1", # c.f. PR #2079
@@ -76,7 +75,7 @@ contrib = [
7675
"requests>=2.22.0",
7776
]
7877
backends = ["pyhf[jax,minuit]"]
79-
all = ["pyhf[backends,xmlio,contrib,shellcomplete]"]
78+
all = ["pyhf[backends,xmlio,contrib]"]
8079

8180
# Developer extras
8281
[dependency-groups]

src/pyhf/cli/complete.py

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,66 @@
1-
'''Shell completions for pyhf.'''
1+
"""Shell completions for pyhf."""
22

33
import click
44

5-
try:
6-
import click_completion
7-
8-
click_completion.init()
9-
10-
@click.command(help='Generate shell completion code.', name='completions')
11-
@click.argument(
12-
'shell',
13-
required=False,
14-
type=click_completion.DocumentedChoice(click_completion.core.shells),
15-
)
16-
def cli(shell):
17-
'''Generate shell completion code for various shells.'''
18-
click.echo(click_completion.core.get_code(shell, prog_name='pyhf'))
19-
20-
except ImportError:
21-
22-
@click.command(help='Generate shell completion code.', name='completions')
23-
@click.argument('shell', default=None)
24-
def cli(shell):
25-
"""Placeholder for shell completion code generatioon function if necessary dependency is missing."""
26-
click.secho(
27-
"This requires the click_completion module.\n"
28-
"You can install it with the shellcomplete extra:\n"
29-
"python -m pip install 'pyhf[shellcomplete]'"
5+
6+
@click.command(help="Generate shell completion code.", name="completions")
7+
@click.argument(
8+
"shell",
9+
required=False,
10+
type=click.Choice(["bash", "zsh", "fish"], case_sensitive=False),
11+
)
12+
def cli(shell):
13+
"""Generate shell completion code for various shells.
14+
15+
Supported shells: bash, zsh, fish
16+
17+
To enable completion, run the appropriate command for your shell:
18+
19+
\b
20+
Bash:
21+
mkdir -p ~/.completions
22+
_PYHF_COMPLETE=bash_source pyhf > ~/.completions/pyhf-complete.sh
23+
echo ". ~/.completions/pyhf-complete.sh" >> ~/.bashrc
24+
25+
\b
26+
Zsh:
27+
mkdir -p ~/.completions
28+
_PYHF_COMPLETE=zsh_source pyhf > ~/.completions/pyhf-complete.zsh
29+
echo ". ~/.completions/pyhf-complete.zsh" >> ~/.zshrc
30+
31+
\b
32+
Fish:
33+
_PYHF_COMPLETE=fish_source pyhf >> ~/.config/fish/completions/pyhf.fish
34+
"""
35+
if shell is None:
36+
click.echo(cli.get_help(click.Context(cli)))
37+
return
38+
39+
click.echo(f"To enable {shell} completion for pyhf run in your {shell} shell:\n")
40+
41+
if shell == "bash":
42+
click.echo(
43+
click.style(
44+
"mkdir -p ~/.completions\n"
45+
+ "_PYHF_COMPLETE=bash_source pyhf > ~/.completions/pyhf-complete.sh\n"
46+
+ 'echo -e "\\n. ~/.completions/pyhf-complete.sh" >> ~/.bashrc\n',
47+
bold=True,
48+
)
49+
)
50+
elif shell == "zsh":
51+
click.echo(
52+
click.style(
53+
"mkdir -p ~/.completions\n"
54+
+ "_PYHF_COMPLETE=zsh_source pyhf > ~/.completions/pyhf-complete.zsh\n"
55+
+ 'echo -e "\\n. ~/.completions/pyhf-complete.zsh" >> ~/.zshrc\n',
56+
bold=True,
57+
)
58+
)
59+
elif shell == "fish":
60+
click.echo(
61+
click.style(
62+
"_PYHF_COMPLETE=fish_source pyhf >> ~/.config/fish/completions/pyhf.fish\n",
63+
bold=True,
64+
)
3065
)
66+
click.echo("and then source your shell configuration or restart your shell.")

tests/test_cli.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
11
from click.testing import CliRunner
2-
import sys
3-
import importlib
42

53

6-
def test_shllcomplete_cli(isolate_modules):
4+
def test_shell_completion_cli_bash():
75
from pyhf.cli.complete import cli
86

97
runner = CliRunner()
10-
result = runner.invoke(cli, ['bash'])
11-
assert 'complete -F _pyhf_completion -o default pyhf' in result.output
8+
result = runner.invoke(cli, ["bash"])
9+
assert result.exit_code == 0
10+
assert "_PYHF_COMPLETE=bash_source" in result.output
11+
assert ".bashrc" in result.output
1212

1313

14-
def test_shllcomplete_cli_missing_extra(isolate_modules):
15-
sys.modules['click_completion'] = None
16-
importlib.reload(sys.modules['pyhf.cli.complete'])
14+
def test_shell_completion_cli_zsh():
1715
from pyhf.cli.complete import cli
1816

1917
runner = CliRunner()
20-
result = runner.invoke(cli, ['bash'])
21-
assert 'You can install it with the shellcomplete extra' in result.output
18+
result = runner.invoke(cli, ["zsh"])
19+
assert result.exit_code == 0
20+
assert "_PYHF_COMPLETE=zsh_source" in result.output
21+
assert ".zshrc" in result.output
22+
23+
24+
def test_shell_completion_cli_fish():
25+
from pyhf.cli.complete import cli
26+
27+
runner = CliRunner()
28+
result = runner.invoke(cli, ["fish"])
29+
assert result.exit_code == 0
30+
assert "_PYHF_COMPLETE=fish_source" in result.output
31+
assert "fish/completions" in result.output
32+
33+
34+
def test_shell_completion_cli_no_shell():
35+
from pyhf.cli.complete import cli
36+
37+
runner = CliRunner()
38+
result = runner.invoke(cli)
39+
assert result.exit_code == 0
40+
assert "Generate shell completion code" in result.output

0 commit comments

Comments
 (0)