From a268d25c87d8fb42c2b295247cc262fffc902111 Mon Sep 17 00:00:00 2001 From: N283T Date: Thu, 16 Apr 2026 12:50:38 +0900 Subject: [PATCH 1/2] feat: add --version / -V flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prints "mmcif-dict " and exits 0. Version is sourced from build.zig.zon via b.addOptions() so it stays in lockstep with the package version automatically — no hardcoded constant to forget. Closes #36 --- CHANGELOG.md | 6 ++++++ README.md | 3 ++- build.zig | 7 +++++++ src/main.zig | 8 +++++++- tests/test_smoke.py | 31 +++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 2 deletions(-) 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..dbd0d15 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -199,5 +199,36 @@ 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 + + if __name__ == "__main__": raise SystemExit(pytest.main([__file__, "-v"])) From ddb8afee4b2960a4151e42349bdf9a2b6684da22 Mon Sep 17 00:00:00 2001 From: N283T Date: Thu, 16 Apr 2026 12:54:00 +0900 Subject: [PATCH 2/2] test: also assert -V is advertised in --help output --- tests/test_smoke.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_smoke.py b/tests/test_smoke.py index dbd0d15..067666a 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -228,6 +228,8 @@ 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__":