diff --git a/java_codebase_rag/_version.py b/java_codebase_rag/_version.py new file mode 100644 index 0000000..2cd0fd2 --- /dev/null +++ b/java_codebase_rag/_version.py @@ -0,0 +1,35 @@ +"""Version string for the CLI ``--version`` flag. + +The single source of truth is the installed distribution metadata +(``java-codebase-rag`` in pyproject.toml), read via :mod:`importlib.metadata` +so a pyproject bump propagates with no second hardcoded copy. +:func:`version_string` appends the CPython version for the +`` (python )`` format chosen for the ``--version`` flag. + +Stdlib-only on purpose: this is imported at module load by both CLIs, and +``jrag`` keeps ``build_parser()`` free of torch / sentence_transformers / mcp_v2. +""" +from __future__ import annotations + +import platform +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _dist_version + +_PACKAGE = "java-codebase-rag" + + +def package_version() -> str: + """Installed distribution version, or ``"unknown"`` if metadata is absent. + + Absent only when run from a raw checkout without ``pip install -e``; the + test suite (``conftest.py``) enforces editable install, so this is defensive. + """ + try: + return _dist_version(_PACKAGE) + except PackageNotFoundError: # pragma: no cover - defensive + return "unknown" + + +def version_string(prog: str) -> str: + """Formatted ``--version`` output: `` (python )``.""" + return f"{prog} {package_version()} (python {platform.python_version()})" diff --git a/java_codebase_rag/cli.py b/java_codebase_rag/cli.py index 560326b..fb9967e 100644 --- a/java_codebase_rag/cli.py +++ b/java_codebase_rag/cli.py @@ -25,6 +25,7 @@ write_config_source_pointer, ) from java_codebase_rag._fdlimit import raise_fd_limit +from java_codebase_rag._version import version_string from java_codebase_rag.pipeline import ( clip, is_cocoindex_preflight_blocker, @@ -919,6 +920,11 @@ def build_parser() -> argparse.ArgumentParser: formatter_class=argparse.RawDescriptionHelpFormatter, exit_on_error=False, ) + parser.add_argument( + "--version", + action="version", + version=version_string(parser.prog), + ) subparsers = parser.add_subparsers(dest="subcommand") init = subparsers.add_parser( diff --git a/java_codebase_rag/jrag.py b/java_codebase_rag/jrag.py index ed95426..fd35b41 100644 --- a/java_codebase_rag/jrag.py +++ b/java_codebase_rag/jrag.py @@ -30,6 +30,7 @@ from java_codebase_rag._fdlimit import raise_fd_limit from java_codebase_rag._stdio import force_utf8_stdio +from java_codebase_rag._version import version_string __all__ = ["build_parser", "main", "_console_script_main"] @@ -334,6 +335,11 @@ def build_parser() -> argparse.ArgumentParser: formatter_class=argparse.RawDescriptionHelpFormatter, exit_on_error=False, ) + parser.add_argument( + "--version", + action="version", + version=version_string(parser.prog), + ) subparsers = parser.add_subparsers(dest="command", parser_class=_EnvelopeArgumentParser) # Common flags applied per command via parents=[_common_parser()]. NOT diff --git a/tests/test_version_flag.py b/tests/test_version_flag.py new file mode 100644 index 0000000..b07c105 --- /dev/null +++ b/tests/test_version_flag.py @@ -0,0 +1,39 @@ +"""``--version`` flag for the ``java-codebase-rag`` and ``jrag`` CLIs. + +Format contract: `` (python )``, exit 0. The version is +read from installed metadata, so assertions match :func:`version_string` rather +than a hardcoded literal (it bumps every release). +""" +from __future__ import annotations + +import re + +from java_codebase_rag import cli, jrag +from java_codebase_rag._version import version_string + +_PY_VER_RE = re.compile(r"\(python \d+\.\d+\.\d+\)") + + +def test_java_codebase_rag_version_flag(capsys): + rc = cli.main(["--version"]) + + assert rc == 0 + out = capsys.readouterr().out.strip() + assert out == version_string("java-codebase-rag") + assert _PY_VER_RE.search(out) + + +def test_jrag_version_flag(capsys): + rc = jrag.main(["--version"]) + + assert rc == 0 + out = capsys.readouterr().out.strip() + assert out == version_string("jrag") + assert _PY_VER_RE.search(out) + + +def test_version_flag_rejected_after_subcommand(): + """Top-level only: ``jrag --version`` is a usage error, not a version print.""" + rc = jrag.main(["status", "--version"]) + + assert rc != 0