From 74f781e800d9a256225d7d065a4c35ae5528a615 Mon Sep 17 00:00:00 2001 From: Charles Graham Date: Sat, 28 Mar 2026 08:56:27 -0700 Subject: [PATCH 1/2] Add shell completion for click to rtd --- docs/cli.rst | 1 + docs/cli/setup.rst | 4 ++ docs/cli/shell_completion.rst | 107 ++++++++++++++++++++++++++++++++++ docs/index.rst | 7 +++ 4 files changed, 119 insertions(+) create mode 100644 docs/cli/shell_completion.rst diff --git a/docs/cli.rst b/docs/cli.rst index cc4a7d9..ca61cf9 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -4,6 +4,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 23fa058..6865bae 100644 --- a/docs/cli/setup.rst +++ b/docs/cli/setup.rst @@ -13,6 +13,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 @@ -115,6 +118,7 @@ look correct before you point the command at a production config. See also -------- +- :doc:`Shell Completion ` - :doc:`csv2cwms ` - :doc:`Common API Arguments ` - :doc:`Complete config example ` 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 37eee34..3167af3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,11 +1,18 @@ cwms-cli Documentation ====================== +Start here: + +- :doc:`Installation and Setup ` +- :doc:`Shell Completion ` +- :doc:`CLI Reference ` + .. toctree:: :maxdepth: 2 cli cli/setup + cli/shell_completion cli/api_arguments cli/cda_regex cli/csv2cwms From 41a11fd9e55e915e0772e02e7b8c70e2e24daf7a Mon Sep 17 00:00:00 2001 From: Charles Graham Date: Sat, 28 Mar 2026 08:57:11 -0700 Subject: [PATCH 2/2] Ensure shell completion is shown on general help page in cli --- cwmscli/utils/click_help.py | 14 ++++++++++++++ tests/cli/test_all_commands_help.py | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cwmscli/utils/click_help.py b/cwmscli/utils/click_help.py index 26599df..75ec120 100644 --- a/cwmscli/utils/click_help.py +++ b/cwmscli/utils/click_help.py @@ -9,6 +9,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: @@ -57,6 +58,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 _inject_help_header(help_text: str, ctx: click.Context) -> str: lines = help_text.splitlines() if not lines: @@ -68,14 +77,19 @@ 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) if lines[0].startswith("Usage:"): lines.insert(1, version_line) if docs_line is not None: lines.insert(2, docs_line) + if shell_completion_line is not None: + lines.insert(3 if docs_line is not None else 2, shell_completion_line) else: lines.insert(0, version_line) if docs_line is not None: lines.insert(1, docs_line) + if shell_completion_line is not None: + lines.insert(2 if docs_line is not None else 1, shell_completion_line) return "\n".join(lines) diff --git a/tests/cli/test_all_commands_help.py b/tests/cli/test_all_commands_help.py index 2e59113..5b25102 100644 --- a/tests/cli/test_all_commands_help.py +++ b/tests/cli/test_all_commands_help.py @@ -2,7 +2,7 @@ from click.testing import CliRunner from cwmscli.__main__ import cli -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 ## Expectations @@ -39,6 +39,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 def test_root_version_flag(runner): @@ -109,5 +110,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