diff --git a/CHANGELOG.md b/CHANGELOG.md index 391e2eb..47874b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- `--version` / `-V` flag prints the CLI version (e.g. `mmcif-dict 0.3.0`) and + exits; the version is sourced from `build.zig.zon` via a build option so it + stays in sync with the release automatically (#36) + ## [0.3.0] - 2026-04-16 ### Removed diff --git a/README.md b/README.md index e81bcac..01550bf 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,8 @@ The runtime format is a zero-copy native binary (`.mdict`). Produce one with | `--json` | Output in JSON format | | `--dict PATH` | Path to dictionary (`.mdict`) | | `--name NAME` | Select named cache (default: `pdbx`) | -| `--help` | Show usage | +| `--version`, `-V` | Show version and exit | +| `--help`, `-h` | Show usage | ## Environment Variables diff --git a/build.zig b/build.zig index 6590d94..7db4636 100644 --- a/build.zig +++ b/build.zig @@ -1,9 +1,15 @@ const std = @import("std"); +const zon = @import("build.zig.zon"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + // Expose the package version from build.zig.zon as a build option so the + // CLI can print it for --version without hardcoding the string twice. + const options = b.addOptions(); + options.addOption([]const u8, "version", zon.version); + const exe = b.addExecutable(.{ .name = "mmcif-dict", .root_module = b.createModule(.{ @@ -12,6 +18,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }), }); + exe.root_module.addOptions("build_options", options); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); diff --git a/src/main.zig b/src/main.zig index f2e5fb7..346be59 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const build_options = @import("build_options"); const dic_loader = @import("dic_loader.zig"); const dict = @import("dict.zig"); const fetch = @import("fetch.zig"); @@ -21,7 +22,8 @@ const usage = \\ --json Output in JSON format \\ --dict PATH Path to dictionary (.mdict) \\ --name NAME Select named cache (default: pdbx) - \\ --help Show this help + \\ --version, -V Show version and exit + \\ --help, -h Show this help \\ \\Environment: \\ MMCIF_DICT_PATH Default path to dictionary file (.mdict) @@ -60,6 +62,10 @@ pub fn main() !void { try w.writeAll(usage); try w.flush(); return; + } else if (std.mem.eql(u8, arg, "--version") or std.mem.eql(u8, arg, "-V")) { + try w.print("mmcif-dict {s}\n", .{build_options.version}); + try w.flush(); + return; } else if (std.mem.eql(u8, arg, "--dict")) { i += 1; if (i >= args.len) { diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 3231d6c..067666a 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -199,5 +199,38 @@ def test_invalid_name_rejected() -> None: assert "invalid" in r.stderr.lower() +def _expected_version() -> str: + """Parse the version string out of build.zig.zon without importing Zig.""" + text = PROJECT_ROOT.joinpath("build.zig.zon").read_text() + for line in text.splitlines(): + stripped = line.strip() + # Match `.version = "X"` exactly; reject siblings like `.version_foo`. + if stripped.startswith(".version") and "=" in stripped and '"' in stripped: + key = stripped.split("=", 1)[0].strip() + if key == ".version": + return stripped.split('"', 2)[1] + raise AssertionError("no .version line in build.zig.zon") + + +def test_version_long_flag() -> None: + r = _run("--version") + assert r.returncode == 0, r.stderr + assert r.stdout.strip() == f"mmcif-dict {_expected_version()}" + + +def test_version_short_flag() -> None: + r = _run("-V") + assert r.returncode == 0, r.stderr + assert r.stdout.strip() == f"mmcif-dict {_expected_version()}" + + +def test_help_mentions_version_flag() -> None: + r = _run("--help") + assert r.returncode == 0, r.stderr + assert "--version" in r.stdout + # Guard against someone quietly dropping the short form from usage text. + assert "-V" in r.stdout + + if __name__ == "__main__": raise SystemExit(pytest.main([__file__, "-v"]))