-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add shell-completions subcommand (#76)
- Loading branch information
Showing
5 changed files
with
220 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import sys | ||
from pathlib import Path | ||
|
||
from rich import print | ||
|
||
SUPPORTED_SHELLS = ["bash", "zsh", "fish"] | ||
|
||
|
||
def is_autocomplete_installed(file: Path) -> bool: | ||
if not file.exists(): | ||
print(f"[yellow]{file} does not exist, creating file") | ||
with open(file, "w") as f: | ||
f.write("") | ||
|
||
# https://click.palletsprojects.com/en/8.1.x/shell-completion/#enabling-completion | ||
if "_GOOSE_COMPLETE" in open(file).read(): | ||
print(f"auto-completion already installed in {file}") | ||
return True | ||
return False | ||
|
||
|
||
def setup_bash(install: bool) -> None: | ||
bashrc = Path("~/.bashrc").expanduser() | ||
if install: | ||
if is_autocomplete_installed(bashrc): | ||
return | ||
f = open(bashrc, "a") | ||
else: | ||
f = sys.stdout | ||
print(f"# add the following to your bash config, typically {bashrc}") | ||
|
||
with f: | ||
f.write('eval "$(_GOOSE_COMPLETE=bash_source goose)"\n') | ||
|
||
if install: | ||
print(f"installed auto-completion to {bashrc}") | ||
print(f"run `source {bashrc}` to enable auto-completion") | ||
|
||
|
||
def setup_fish(install: bool) -> None: | ||
completion_dir = Path("~/.config/fish/completions").expanduser() | ||
if not completion_dir.exists(): | ||
completion_dir.mkdir(parents=True, exist_ok=True) | ||
|
||
completion_file = completion_dir / "goose.fish" | ||
if install: | ||
if is_autocomplete_installed(completion_file): | ||
return | ||
f = open(completion_file, "a") | ||
else: | ||
f = sys.stdout | ||
print(f"# add the following to your fish config, typically {completion_file}") | ||
|
||
with f: | ||
f.write("_GOOSE_COMPLETE=fish_source goose | source\n") | ||
|
||
if install: | ||
print(f"installed auto-completion to {completion_file}") | ||
|
||
|
||
def setup_zsh(install: bool) -> None: | ||
zshrc = Path("~/.zshrc").expanduser() | ||
if install: | ||
if is_autocomplete_installed(zshrc): | ||
return | ||
f = open(zshrc, "a") | ||
else: | ||
f = sys.stdout | ||
print(f"# add the following to your zsh config, typically {zshrc}") | ||
|
||
with f: | ||
f.write("autoload -U +X compinit && compinit\n") | ||
f.write("autoload -U +X bashcompinit && bashcompinit\n") | ||
f.write('eval "$(_GOOSE_COMPLETE=zsh_source goose)"\n') | ||
|
||
if install: | ||
print(f"installed auto-completion to {zshrc}") | ||
print(f"run `source {zshrc}` to enable auto-completion") | ||
|
||
|
||
def setup_autocomplete(shell: str, install: bool) -> None: | ||
"""Installs shell completions for goose | ||
Args: | ||
shell (str): shell to install completions for | ||
install (bool): whether to install or generate completions | ||
""" | ||
|
||
match shell: | ||
case "bash": | ||
setup_bash(install=install) | ||
|
||
case "zsh": | ||
setup_zsh(install=install) | ||
|
||
case "fish": | ||
setup_fish(install=install) | ||
|
||
case _: | ||
print(f"Shell {shell} not supported") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import sys | ||
import unittest.mock as mock | ||
|
||
from goose.utils.autocomplete import SUPPORTED_SHELLS, is_autocomplete_installed, setup_autocomplete | ||
|
||
|
||
def test_supported_shells(): | ||
assert SUPPORTED_SHELLS == ["bash", "zsh", "fish"] | ||
|
||
|
||
def test_install_autocomplete(tmp_path): | ||
file = tmp_path / "test_bash_autocomplete" | ||
assert is_autocomplete_installed(file) is False | ||
|
||
file.write_text("_GOOSE_COMPLETE") | ||
assert is_autocomplete_installed(file) is True | ||
|
||
|
||
@mock.patch("sys.stdout") | ||
def test_setup_bash(mocker: mock.MagicMock): | ||
setup_autocomplete("bash", install=False) | ||
sys.stdout.write.assert_called_with('eval "$(_GOOSE_COMPLETE=bash_source goose)"\n') | ||
|
||
|
||
@mock.patch("sys.stdout") | ||
def test_setup_zsh(mocker: mock.MagicMock): | ||
setup_autocomplete("zsh", install=False) | ||
sys.stdout.write.assert_called_with('eval "$(_GOOSE_COMPLETE=zsh_source goose)"\n') | ||
|
||
|
||
@mock.patch("sys.stdout") | ||
def test_setup_fish(mocker: mock.MagicMock): | ||
setup_autocomplete("fish", install=False) | ||
sys.stdout.write.assert_called_with("_GOOSE_COMPLETE=fish_source goose | source\n") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from click.testing import CliRunner | ||
from goose.cli.main import get_current_shell, shell_completions | ||
|
||
|
||
def test_get_current_shell(mocker): | ||
mocker.patch("os.getenv", return_value="/bin/bash") | ||
assert get_current_shell() == "bash" | ||
|
||
|
||
def test_shell_completions_install_invalid_combination(): | ||
runner = CliRunner() | ||
result = runner.invoke(shell_completions, ["--install", "--generate", "bash"]) | ||
assert result.exit_code != 0 | ||
assert "Only one of --install or --generate can be specified" in result.output | ||
|
||
|
||
def test_shell_completions_install_no_option(): | ||
runner = CliRunner() | ||
result = runner.invoke(shell_completions, ["bash"]) | ||
assert result.exit_code != 0 | ||
assert "One of --install or --generate must be specified" in result.output |