diff --git a/cwmscli/utils/click_help.py b/cwmscli/utils/click_help.py index 3eaf8a8..4d9a529 100644 --- a/cwmscli/utils/click_help.py +++ b/cwmscli/utils/click_help.py @@ -10,6 +10,7 @@ from cwmscli.utils.version import get_cwms_cli_version DOCS_BASE_URL = "https://cwms-cli.readthedocs.io/en/latest" +SHELL_COMPLETION_DOCS_URL = f"{DOCS_BASE_URL}/cli/shell_completion.html" def _render_version_line(ctx: click.Context) -> str: @@ -61,6 +62,14 @@ def _render_docs_line(ctx: click.Context) -> Optional[str]: return f"Docs: {colors.c(docs_url, 'blue', bright=True)}" +def _render_shell_completion_line(ctx: click.Context) -> Optional[str]: + if ctx.parent is not None: + return None + return ( + f"Shell completion: {colors.c(SHELL_COMPLETION_DOCS_URL, 'blue', bright=True)}" + ) + + def _command_path(ctx: click.Context) -> str: names: list[str] = [] cur: Optional[click.Context] = ctx @@ -98,6 +107,7 @@ def _inject_help_header(help_text: str, ctx: click.Context) -> str: version_line = _render_version_line(ctx) docs_line = _render_docs_line(ctx) + shell_completion_line = _render_shell_completion_line(ctx) maintainers_line = _render_maintainers_line(ctx) if lines[0].startswith("Usage:"): lines.insert(1, version_line) @@ -106,6 +116,11 @@ def _inject_help_header(help_text: str, ctx: click.Context) -> str: lines.insert(3, maintainers_line) else: lines.insert(2, maintainers_line) + if shell_completion_line is not None: + insert_at = 3 if docs_line is not None else 2 + if docs_line is not None: + insert_at += 1 + lines.insert(insert_at, shell_completion_line) else: lines.insert(0, version_line) if docs_line is not None: @@ -113,6 +128,11 @@ def _inject_help_header(help_text: str, ctx: click.Context) -> str: lines.insert(2, maintainers_line) else: lines.insert(1, maintainers_line) + if shell_completion_line is not None: + insert_at = 2 if docs_line is not None else 1 + if docs_line is not None: + insert_at += 1 + lines.insert(insert_at, shell_completion_line) return "\n".join(lines) diff --git a/docs/cli.rst b/docs/cli.rst index db31cd8..8b33e65 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -6,6 +6,7 @@ CLI reference See also -------- +- :doc:`Shell Completion ` - :doc:`csv2cwms ` - :doc:`CDA Regex Guide ` - :doc:`load location ids-all ` diff --git a/docs/cli/setup.rst b/docs/cli/setup.rst index c45c1f8..56e358a 100644 --- a/docs/cli/setup.rst +++ b/docs/cli/setup.rst @@ -14,6 +14,9 @@ Install the base CLI package: pip install cwms-cli +After installation, see :doc:`Shell Completion ` if you want +tab completion for supported shells. + Some commands require additional libraries that are not installed with the base package. You will be alerted to missing dependencies if you try to run a command that requires an optional library that is not installed. See the next @@ -62,4 +65,6 @@ See :doc:`Common API Arguments ` for environment setup examples. See also -------- +- :doc:`Shell Completion ` +- :doc:`csv2cwms ` - :doc:`Common API Arguments ` diff --git a/docs/cli/shell_completion.rst b/docs/cli/shell_completion.rst new file mode 100644 index 0000000..2fba248 --- /dev/null +++ b/docs/cli/shell_completion.rst @@ -0,0 +1,107 @@ +Shell Completion +================ + +``cwms-cli`` uses Click, which provides built-in shell completion support for +``bash`` (4.4+), ``zsh``, and ``fish`` when the CLI is installed as an entry +point such as ``cwms-cli``. + +Shell completion can suggest: + +- command and subcommand names +- option names +- values for supported parameter types such as choices and paths + +See the official Click shell completion guide for background and advanced +customization: + +- https://click.palletsprojects.com/en/stable/shell-completion/ + +Requirements +------------ + +- Install ``cwms-cli`` so the ``cwms-cli`` executable is available in your + shell. +- Start completion from the installed command name, not ``python -m``. +- Restart your shell after changing your shell startup files. + +Bash +---- + +Add this line to ``~/.bashrc``: + +.. code-block:: bash + + eval "$(_CWMS_CLI_COMPLETE=bash_source cwms-cli)" + +That asks Click to generate the completion script each time a new shell starts. + +If you prefer to generate the script once and source a saved file instead: + +.. code-block:: bash + + _CWMS_CLI_COMPLETE=bash_source cwms-cli > ~/.cwms-cli-complete.bash + +Then add this line to ``~/.bashrc``: + +.. code-block:: bash + + . ~/.cwms-cli-complete.bash + +Zsh +--- + +Add this line to ``~/.zshrc``: + +.. code-block:: zsh + + eval "$(_CWMS_CLI_COMPLETE=zsh_source cwms-cli)" + +If you prefer to generate the script once and source a saved file instead: + +.. code-block:: zsh + + _CWMS_CLI_COMPLETE=zsh_source cwms-cli > ~/.cwms-cli-complete.zsh + +Then add this line to ``~/.zshrc``: + +.. code-block:: zsh + + . ~/.cwms-cli-complete.zsh + +Fish +---- + +Save the generated completion script to Fish's completions directory: + +.. code-block:: fish + + _CWMS_CLI_COMPLETE=fish_source cwms-cli > ~/.config/fish/completions/cwms-cli.fish + +Fish will load that file automatically in new shell sessions. + +Verify Completion +----------------- + +After installing the shell integration and opening a new shell, try: + +.. code-block:: bash + + cwms-cli + +You should see top-level commands such as ``load``, ``usgs``, and ``blob``. +You can also try: + +.. code-block:: bash + + cwms-cli load + cwms-cli blob -- + +Unsupported Shells +------------------ + +Click's built-in shell completion support covers ``bash``, ``zsh``, and +``fish``. For now, ``cwms-cli`` does not document built-in completion setup +for PowerShell or ``cmd.exe``. + +If Windows shell completion becomes a requirement, that will need either a +custom completion integration or a third-party Click shell extension. diff --git a/docs/index.rst b/docs/index.rst index f6474fa..0932f1a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,6 +49,7 @@ Contents :caption: Getting Started cli/setup + cli/shell_completion cli cli/api_arguments diff --git a/tests/cli/test_all_commands_help.py b/tests/cli/test_all_commands_help.py index 9de13b2..b307c75 100644 --- a/tests/cli/test_all_commands_help.py +++ b/tests/cli/test_all_commands_help.py @@ -6,7 +6,7 @@ from cwmscli.__main__ import cli from cwmscli.ownership import format_command_maintainers -from cwmscli.utils.click_help import DOCS_BASE_URL +from cwmscli.utils.click_help import DOCS_BASE_URL, SHELL_COMPLETION_DOCS_URL from cwmscli.utils.version import get_cwms_cli_version @@ -68,6 +68,7 @@ def test_root_help(runner): assert "Usage:" in result.output assert f"Version: {get_cwms_cli_version()}" in result.output assert f"Docs: {DOCS_BASE_URL}/cli.html" in result.output + assert f"Shell completion: {SHELL_COMPLETION_DOCS_URL}" in result.output assert f"Maintainers: {format_command_maintainers('cwms-cli')}" in result.output @@ -143,5 +144,7 @@ def test_every_command_has_help(runner, path, command): path[0], f"{DOCS_BASE_URL}/cli.html#cwms-cli-{path[0]}" ) assert f"Docs: {expected_docs}" in result.output + assert "Shell completion:" not in result.output else: assert "Docs:" not in result.output + assert "Shell completion:" not in result.output