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
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Gather stats for the last month::
did last month

See ``did --help`` for complete list of available stats.
Use ``did --version`` (or ``-V``) to print the installed version.


Options
Expand Down
19 changes: 19 additions & 0 deletions did/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from did import utils
from did.stats import UserStats
from did.utils import log
from did.version import get_version

USAGE = """
did [this|last] [week|month|quarter|year] [options]
Expand All @@ -28,6 +29,15 @@
""".strip()


def _argv_list(arguments: Union[None, str, list[str]]) -> list[str]:
"""Command-line tokens to parse (excluding program name)."""
if arguments is None:
return sys.argv[1:]
if isinstance(arguments, str):
return arguments.split()
return list(arguments)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Options
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -110,6 +120,10 @@ def __init__(self, arguments: Union[None, str, list[str]] = None):
group.add_argument(
"--test", action="store_true",
help="Run a simple smoke test against the github server")
group.add_argument(
"--version", "-V", action="version",
version=f"did {get_version()}",
help="Show program version and exit")

def _prepare_arguments(self, arguments: Union[None, str, list[str]]) -> None:
""" Prepare arguments (both direct and from command line) """
Expand Down Expand Up @@ -209,6 +223,11 @@ def main(arguments: Union[None, str, list[str]] = None

with the list of all gathered stats objects.
"""
argv = _argv_list(arguments)
if "--version" in argv or "-V" in argv:
print(f"did {get_version()}")
raise SystemExit(0)

config = None
try:
config = did.base.Config()
Expand Down
52 changes: 52 additions & 0 deletions did/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Version string for did.

Uses :mod:`importlib.metadata` when the package is installed; falls back to
parsing ``did.spec`` in a source checkout (same scheme as ``setup.py``).
"""

from __future__ import annotations

import re
from pathlib import Path


def get_version() -> str:
"""
Return did version (e.g. ``0.23.1``).

Prefer the installed distribution metadata; otherwise parse ``did.spec``.
"""
try:
from importlib.metadata import PackageNotFoundError, version
except ImportError: # pragma: no cover - Python < 3.8
from importlib_metadata import ( # type: ignore[import-not-found,no-redef]
PackageNotFoundError,
version,
)

try:
return version("did")
except PackageNotFoundError:
pass

parsed = _version_from_spec()
return parsed if parsed is not None else "0.0.0"


def _version_from_spec() -> str | None:
"""Parse ``Version`` and numeric ``Release`` from ``did.spec`` if found."""
here = Path(__file__).resolve().parent
for base in [here.parent, *here.parents]:
spec_path = base / "did.spec"
if not spec_path.is_file():
continue
text = spec_path.read_text(encoding="utf-8")
vm = re.search(r"^Version:\s*(.+)$", text, re.MULTILINE)
rm = re.search(r"^Release:\s*(\d+)", text, re.MULTILINE)
if not vm or not rm:
return None
ver = vm.group(1).strip()
rel = rm.group(1).strip()
return f"{ver}.{rel}"
return None
19 changes: 19 additions & 0 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ def test_invalid_date() -> None:
did.cli.main(argument)


def test_version_flag(capsys: pytest.CaptureFixture[str]) -> None:
""" --version exits before config and prints a version line """
with pytest.raises(SystemExit) as exc:
did.cli.main(["--version"])
assert exc.value.code == 0
out = capsys.readouterr().out.strip()
assert out.startswith("did ")
assert len(out) > len("did ")


def test_version_short_flag(capsys: pytest.CaptureFixture[str]) -> None:
""" -V is an alias for --version """
with pytest.raises(SystemExit) as exc:
did.cli.main(["-V"])
assert exc.value.code == 0
out = capsys.readouterr().out.strip()
assert out.startswith("did ")


def test_conflicting_options() -> None:
""" Complain about conflicting options """
did.base.Config(config=MINIMAL)
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/test_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# coding: utf-8
""" Tests for did.version """

import re

from did.version import get_version


def test_get_version_format() -> None:
""" Version string looks like N.N.N (matches did.spec / install metadata). """
ver = get_version()
assert re.match(r"^\d+\.\d+\.\d+$", ver), ver