Skip to content

Commit 7d1f41a

Browse files
committed
Display version at the top of every command (#127)
This will make it so a user knows what version of cwms-cli they are using I have placed it just below the "usage" line in the help page Tested with `--version` and `--version` to see if a missing version would be replaced with the output > Error: No such option: --versio Did you mean --version? But got another traceback. Resolved that in a commit referencing the PR.
1 parent c2e36e4 commit 7d1f41a

4 files changed

Lines changed: 123 additions & 4 deletions

File tree

cwmscli/__main__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@
88
from cwmscli.commands import commands_cwms
99
from cwmscli.load import __main__ as load
1010
from cwmscli.usgs import usgs_group
11+
from cwmscli.utils.click_help import add_version_to_help_tree
1112
from cwmscli.utils.logging import LoggingConfig, setup_logging
1213
from cwmscli.utils.ssl_errors import is_cert_verify_error, ssl_help_text
14+
from cwmscli.utils.version import get_cwms_cli_version
1315

1416

1517
@click.group(context_settings=dict(help_option_names=["-h", "--help"]))
18+
@click.version_option(
19+
get_cwms_cli_version(),
20+
"--version",
21+
"-V",
22+
message="cwms-cli version %(version)s",
23+
)
1624
@click.option(
1725
"--log-file",
1826
type=click.Path(dir_okay=False, writable=True, resolve_path=True),
@@ -47,6 +55,7 @@ def cli(log_file: Optional[str], no_color: bool, log_level: str) -> None:
4755
cli.add_command(commands_cwms.blob_group)
4856
cli.add_command(commands_cwms.clob_group)
4957
cli.add_command(load.load_group)
58+
add_version_to_help_tree(cli)
5059

5160

5261
def main() -> None:
@@ -61,13 +70,15 @@ def main() -> None:
6170
"on",
6271
}
6372
try:
73+
if len(sys.argv) == 1:
74+
cli.main(args=["--help"], prog_name="cwms-cli", standalone_mode=False)
75+
raise SystemExit(0)
6476
cli(standalone_mode=False)
65-
except click.exceptions.NoArgsIsHelpError as e:
66-
if e.ctx is not None:
67-
click.echo(e.ctx.get_help())
68-
raise SystemExit(0)
6977
except SystemExit:
7078
raise
79+
except click.ClickException as e:
80+
e.show()
81+
raise SystemExit(e.exit_code)
7182
except Exception as e:
7283
if is_cert_verify_error(e) and not debug:
7384
# Keep this short, no stack trace.

cwmscli/utils/click_help.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
5+
import click
6+
7+
from cwmscli.utils import colors
8+
from cwmscli.utils.version import get_cwms_cli_version
9+
10+
11+
def _render_version_line(ctx: click.Context) -> str:
12+
# Match existing CLI color behavior: disable color for non-TTY, --no-color, or --log-file.
13+
argv = sys.argv[1:]
14+
no_color = "--no-color" in argv
15+
has_log_file = ("--log-file" in argv) or any(
16+
arg.startswith("--log-file=") for arg in argv
17+
)
18+
allow_color = sys.stdout.isatty() and (not no_color) and (not has_log_file)
19+
colors.set_enabled(allow_color)
20+
return f"Version: {colors.c(get_cwms_cli_version(), 'cyan', bright=True)}"
21+
22+
23+
def _inject_version_line(help_text: str, ctx: click.Context) -> str:
24+
lines = help_text.splitlines()
25+
if not lines:
26+
return help_text
27+
28+
plain_version_line = f"Version: {get_cwms_cli_version()}"
29+
if any(line.strip() == plain_version_line for line in lines):
30+
return help_text
31+
32+
version_line = _render_version_line(ctx)
33+
if lines[0].startswith("Usage:"):
34+
lines.insert(1, version_line)
35+
else:
36+
lines.insert(0, version_line)
37+
return "\n".join(lines)
38+
39+
40+
def _wrap_get_help(command: click.Command) -> None:
41+
original_get_help = command.get_help
42+
43+
def get_help_with_version(ctx: click.Context) -> str:
44+
return _inject_version_line(original_get_help(ctx), ctx)
45+
46+
command.get_help = get_help_with_version # type: ignore[method-assign]
47+
48+
49+
def add_version_to_help_tree(command: click.Command) -> None:
50+
"""Patch a command tree so every help output shows the CLI version."""
51+
stack = [command]
52+
seen: set[int] = set()
53+
54+
while stack:
55+
cmd = stack.pop()
56+
obj_id = id(cmd)
57+
if obj_id in seen:
58+
continue
59+
seen.add(obj_id)
60+
61+
_wrap_get_help(cmd)
62+
63+
if isinstance(cmd, click.Group):
64+
stack.extend(cmd.commands.values())

cwmscli/utils/version.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from __future__ import annotations
2+
3+
import re
4+
from functools import lru_cache
5+
from importlib import metadata
6+
from pathlib import Path
7+
8+
9+
@lru_cache(maxsize=1)
10+
def get_cwms_cli_version() -> str:
11+
"""Return installed cwms-cli version, with pyproject fallback for source runs."""
12+
try:
13+
return metadata.version("cwms-cli")
14+
except metadata.PackageNotFoundError:
15+
pass
16+
17+
pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
18+
if not pyproject.exists():
19+
return "unknown"
20+
21+
text = pyproject.read_text(encoding="utf-8")
22+
23+
# Prefer the [tool.poetry] version declaration.
24+
in_poetry_section = False
25+
for line in text.splitlines():
26+
stripped = line.strip()
27+
if stripped.startswith("[") and stripped.endswith("]"):
28+
in_poetry_section = stripped == "[tool.poetry]"
29+
continue
30+
if in_poetry_section:
31+
m = re.match(r'^version\s*=\s*"([^"]+)"\s*$', stripped)
32+
if m:
33+
return m.group(1)
34+
35+
return "unknown"

tests/cli/test_all_commands_help.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from click.testing import CliRunner
33

44
from cwmscli.__main__ import cli
5+
from cwmscli.utils.version import get_cwms_cli_version
56

67
## Expectations
78
# - The help commands should run without requiring an import
@@ -35,6 +36,13 @@ def test_root_help(runner):
3536
result = runner.invoke(cli, ["--help"])
3637
assert result.exit_code == 0
3738
assert "Usage:" in result.output
39+
assert f"Version: {get_cwms_cli_version()}" in result.output
40+
41+
42+
def test_root_version_flag(runner):
43+
result = runner.invoke(cli, ["--version"])
44+
assert result.exit_code == 0
45+
assert f"cwms-cli version {get_cwms_cli_version()}" in result.output
3846

3947

4048
@pytest.mark.parametrize("path,command", list(iter_commands(cli)))
@@ -47,3 +55,4 @@ def test_every_command_has_help(runner, path, command):
4755
result = runner.invoke(cli, args)
4856
assert result.exit_code == 0, f"Failed on: {' '.join(args)}"
4957
assert "Usage:" in result.output
58+
assert f"Version: {get_cwms_cli_version()}" in result.output

0 commit comments

Comments
 (0)