Skip to content
Open
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
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ Homepage = "https://github.com/scikit-hep/pyhf"
"Source Code" = "https://github.com/scikit-hep/pyhf"

[project.optional-dependencies]
shellcomplete = ["click_completion"]
jax = [
"jax>=0.4.1", # c.f. PR #2079
"jaxlib>=0.4.1", # c.f. PR #2079
Expand All @@ -76,7 +75,7 @@ contrib = [
"requests>=2.22.0",
]
backends = ["pyhf[jax,minuit]"]
all = ["pyhf[backends,xmlio,contrib,shellcomplete]"]
all = ["pyhf[backends,xmlio,contrib]"]

# Developer extras
[dependency-groups]
Expand Down
80 changes: 56 additions & 24 deletions src/pyhf/cli/complete.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,62 @@
'''Shell completions for pyhf.'''
"""Shell completions for pyhf."""

import click

try:
import click_completion

click_completion.init()
@click.command(help="Generate shell completion code.", name="completions")
@click.argument(
"shell",
required=False,
type=click.Choice(["bash", "zsh", "fish"], case_sensitive=False),
)
def cli(shell):
"""Generate shell completion code for various shells.
@click.command(help='Generate shell completion code.', name='completions')
@click.argument(
'shell',
required=False,
type=click_completion.DocumentedChoice(click_completion.core.shells),
)
def cli(shell):
'''Generate shell completion code for various shells.'''
click.echo(click_completion.core.get_code(shell, prog_name='pyhf'))

except ImportError:

@click.command(help='Generate shell completion code.', name='completions')
@click.argument('shell', default=None)
def cli(shell):
"""Placeholder for shell completion code generatioon function if necessary dependency is missing."""
click.secho(
"This requires the click_completion module.\n"
"You can install it with the shellcomplete extra:\n"
"python -m pip install 'pyhf[shellcomplete]'"
Supported shells: bash, zsh, fish
To enable completion, run the appropriate command for your shell:
\b
Bash:
mkdir -p ~/.completions
_PYHF_COMPLETE=bash_source pyhf > ~/.completions/pyhf-complete.sh
echo -e "\n. ~/.completions/pyhf-complete.sh" >> ~/.bashrc
\b
Zsh:
mkdir -p ~/.completions
_PYHF_COMPLETE=zsh_source pyhf > ~/.completions/pyhf-complete.zsh
echo -e "\n. ~/.completions/pyhf-complete.zsh" >> ~/.zshrc
\b
Fish:
_PYHF_COMPLETE=fish_source pyhf >> ~/.config/fish/completions/pyhf.fish
Comment on lines +15 to +33
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shell completion instructions are duplicated in both the docstring (lines 20-33) and the instructions dictionary (lines 42-55). This creates a maintenance burden where updates must be made in two places. Consider removing the detailed instructions from the docstring and keeping only the high-level description, or generate the help text dynamically from the instructions dictionary.

Suggested change
Supported shells: bash, zsh, fish
To enable completion, run the appropriate command for your shell:
\b
Bash:
mkdir -p ~/.completions
_PYHF_COMPLETE=bash_source pyhf > ~/.completions/pyhf-complete.sh
echo -e "\n. ~/.completions/pyhf-complete.sh" >> ~/.bashrc
\b
Zsh:
mkdir -p ~/.completions
_PYHF_COMPLETE=zsh_source pyhf > ~/.completions/pyhf-complete.zsh
echo -e "\n. ~/.completions/pyhf-complete.zsh" >> ~/.zshrc
\b
Fish:
_PYHF_COMPLETE=fish_source pyhf >> ~/.config/fish/completions/pyhf.fish
Supported shells: bash, zsh, fish.
When run, this command will print instructions for enabling shell completion
for the specified shell.

Copilot uses AI. Check for mistakes.
"""
if shell is None:
click.echo(cli.get_help(click.Context(cli)))
return

click.echo(f"To enable {shell} completion for pyhf run in your {shell} shell:\n")

instructions = {
"bash": (
"mkdir -p ~/.completions\n"
"_PYHF_COMPLETE=bash_source pyhf > ~/.completions/pyhf-complete.sh\n"
'echo -e "\\n. ~/.completions/pyhf-complete.sh" >> ~/.bashrc\n'
),
"zsh": (
"mkdir -p ~/.completions\n"
"_PYHF_COMPLETE=zsh_source pyhf > ~/.completions/pyhf-complete.zsh\n"
'echo -e "\\n. ~/.completions/pyhf-complete.zsh" >> ~/.zshrc\n'
),
"fish": (
"_PYHF_COMPLETE=fish_source pyhf >> ~/.config/fish/completions/pyhf.fish\n"
),
}
click.echo(
click.style(
instructions[shell],
bold=True,
)
)
click.echo("and then source your shell configuration or restart your shell.")
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing capitalization at the start of the sentence. The sentence should begin with a capital letter for consistency with standard English grammar.

Suggested change
click.echo("and then source your shell configuration or restart your shell.")
click.echo("And then source your shell configuration or restart your shell.")

Copilot uses AI. Check for mistakes.
39 changes: 29 additions & 10 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
from click.testing import CliRunner
import sys
import importlib


def test_shllcomplete_cli(isolate_modules):
def test_shell_completion_cli_bash():
from pyhf.cli.complete import cli

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


def test_shllcomplete_cli_missing_extra(isolate_modules):
sys.modules['click_completion'] = None
importlib.reload(sys.modules['pyhf.cli.complete'])
def test_shell_completion_cli_zsh():
from pyhf.cli.complete import cli

runner = CliRunner()
result = runner.invoke(cli, ['bash'])
assert 'You can install it with the shellcomplete extra' in result.output
result = runner.invoke(cli, ["zsh"])
assert result.exit_code == 0
assert "_PYHF_COMPLETE=zsh_source" in result.output
assert ".zshrc" in result.output


def test_shell_completion_cli_fish():
from pyhf.cli.complete import cli

runner = CliRunner()
result = runner.invoke(cli, ["fish"])
assert result.exit_code == 0
assert "_PYHF_COMPLETE=fish_source" in result.output
assert "fish/completions" in result.output


def test_shell_completion_cli_no_shell():
from pyhf.cli.complete import cli

runner = CliRunner()
result = runner.invoke(cli)
assert result.exit_code == 0
assert "Generate shell completion code" in result.output