migrate version to new cli framework
This commit is contained in:
parent
722ddc0e98
commit
ef21be8603
2 changed files with 91 additions and 57 deletions
|
|
@ -12,31 +12,51 @@ const builtin = @import("builtin");
|
||||||
const zfin = @import("../root.zig");
|
const zfin = @import("../root.zig");
|
||||||
const version = @import("../version.zig");
|
const version = @import("../version.zig");
|
||||||
const cli = @import("common.zig");
|
const cli = @import("common.zig");
|
||||||
|
const framework = @import("framework.zig");
|
||||||
const Date = @import("../Date.zig");
|
const Date = @import("../Date.zig");
|
||||||
|
|
||||||
/// Run the version command.
|
pub const ParsedArgs = struct {
|
||||||
///
|
verbose: bool = false,
|
||||||
/// `args` is the slice after `zfin version` (expects `--verbose`/`-v` or
|
};
|
||||||
/// nothing). Unknown args produce an error on stderr.
|
|
||||||
pub fn run(
|
pub const meta = struct {
|
||||||
io: std.Io,
|
pub const name: []const u8 = "version";
|
||||||
config: zfin.Config,
|
pub const group: framework.Group = .infra;
|
||||||
args: []const []const u8,
|
pub const synopsis: []const u8 = "Show zfin version and build info";
|
||||||
out: *std.Io.Writer,
|
pub const help: []const u8 =
|
||||||
) !void {
|
\\Usage: zfin version [-v|--verbose]
|
||||||
var verbose = false;
|
\\
|
||||||
for (args) |a| {
|
\\Print zfin's version + build date. With `--verbose`/`-v`, also
|
||||||
|
\\prints the Zig compiler version, build mode, build target,
|
||||||
|
\\resolved ZFIN_HOME, and cache directory — useful for bug
|
||||||
|
\\reports.
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
framework.validateCommandModule(@This());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse `--verbose`/`-v`. Unknown args produce an error on stderr.
|
||||||
|
pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs {
|
||||||
|
var parsed: ParsedArgs = .{};
|
||||||
|
for (cmd_args) |a| {
|
||||||
if (std.mem.eql(u8, a, "--verbose") or std.mem.eql(u8, a, "-v")) {
|
if (std.mem.eql(u8, a, "--verbose") or std.mem.eql(u8, a, "-v")) {
|
||||||
verbose = true;
|
parsed.verbose = true;
|
||||||
} else {
|
} else {
|
||||||
try cli.stderrPrint(io, "Error: unknown argument to 'version': ");
|
try cli.stderrPrint(ctx.io, "Error: unknown argument to 'version': ");
|
||||||
try cli.stderrPrint(io, a);
|
try cli.stderrPrint(ctx.io, a);
|
||||||
try cli.stderrPrint(io, "\n");
|
try cli.stderrPrint(ctx.io, "\n");
|
||||||
return error.UnexpectedArg;
|
return error.UnexpectedArg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
try writeVersion(out, config, verbose);
|
/// Run the version command.
|
||||||
|
pub fn run(ctx: *framework.RunCtx, parsed: ParsedArgs) !void {
|
||||||
|
try writeVersion(ctx.out, ctx.config, parsed.verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render version info into `out`. Separated from `run` so tests can
|
/// Render version info into `out`. Separated from `run` so tests can
|
||||||
|
|
@ -95,11 +115,61 @@ fn stubConfig(zfin_home: ?[]const u8, cache_dir: []const u8) zfin.Config {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a minimal `RunCtx` for tests. Most fields are unused by the
|
||||||
|
/// version command's `run` path; we set what writeVersion reads.
|
||||||
|
fn stubCtx(out: *std.Io.Writer, cfg: zfin.Config) framework.RunCtx {
|
||||||
|
return .{
|
||||||
|
.io = std.testing.io,
|
||||||
|
.allocator = std.testing.allocator,
|
||||||
|
.gpa = std.testing.allocator,
|
||||||
|
.environ_map = undefined,
|
||||||
|
.config = cfg,
|
||||||
|
.svc = null,
|
||||||
|
.globals = .{},
|
||||||
|
.today = Date.fromYmd(2026, 5, 9),
|
||||||
|
.now_s = 0,
|
||||||
|
.color = false,
|
||||||
|
.out = out,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseArgs: no args → verbose=false" {
|
||||||
|
var ctx: framework.RunCtx = undefined;
|
||||||
|
ctx.io = std.testing.io;
|
||||||
|
const args = [_][]const u8{};
|
||||||
|
const parsed = try parseArgs(&ctx, &args);
|
||||||
|
try std.testing.expect(!parsed.verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseArgs: --verbose sets verbose" {
|
||||||
|
var ctx: framework.RunCtx = undefined;
|
||||||
|
ctx.io = std.testing.io;
|
||||||
|
const args = [_][]const u8{"--verbose"};
|
||||||
|
const parsed = try parseArgs(&ctx, &args);
|
||||||
|
try std.testing.expect(parsed.verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseArgs: -v short form" {
|
||||||
|
var ctx: framework.RunCtx = undefined;
|
||||||
|
ctx.io = std.testing.io;
|
||||||
|
const args = [_][]const u8{"-v"};
|
||||||
|
const parsed = try parseArgs(&ctx, &args);
|
||||||
|
try std.testing.expect(parsed.verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseArgs: unknown flag errors" {
|
||||||
|
var ctx: framework.RunCtx = undefined;
|
||||||
|
ctx.io = std.testing.io;
|
||||||
|
const args = [_][]const u8{"--bogus"};
|
||||||
|
try std.testing.expectError(error.UnexpectedArg, parseArgs(&ctx, &args));
|
||||||
|
}
|
||||||
|
|
||||||
test "run: no args prints single-line banner" {
|
test "run: no args prints single-line banner" {
|
||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
const cfg = stubConfig(null, "/tmp/test-cache");
|
const cfg = stubConfig(null, "/tmp/test-cache");
|
||||||
try run(std.testing.io, cfg, &.{}, &w);
|
var ctx = stubCtx(&w, cfg);
|
||||||
|
try run(&ctx, .{});
|
||||||
const out = w.buffered();
|
const out = w.buffered();
|
||||||
|
|
||||||
// Banner shape: starts with "zfin ", contains " (built ", ends with newline.
|
// Banner shape: starts with "zfin ", contains " (built ", ends with newline.
|
||||||
|
|
@ -121,8 +191,8 @@ test "run: --verbose includes all diagnostic fields" {
|
||||||
var buf: [2048]u8 = undefined;
|
var buf: [2048]u8 = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
const cfg = stubConfig("/some/zfin/home", "/tmp/expected-cache-dir");
|
const cfg = stubConfig("/some/zfin/home", "/tmp/expected-cache-dir");
|
||||||
const args = [_][]const u8{"--verbose"};
|
var ctx = stubCtx(&w, cfg);
|
||||||
try run(std.testing.io, cfg, &args, &w);
|
try run(&ctx, .{ .verbose = true });
|
||||||
const out = w.buffered();
|
const out = w.buffered();
|
||||||
|
|
||||||
// Banner still first line
|
// Banner still first line
|
||||||
|
|
@ -144,33 +214,6 @@ test "run: --verbose includes all diagnostic fields" {
|
||||||
try std.testing.expect(std.mem.indexOf(u8, out, builtin.zig_version_string) != null);
|
try std.testing.expect(std.mem.indexOf(u8, out, builtin.zig_version_string) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "run: -v short form equivalent to --verbose" {
|
|
||||||
var buf_long: [2048]u8 = undefined;
|
|
||||||
var buf_short: [2048]u8 = undefined;
|
|
||||||
var w_long: std.Io.Writer = .fixed(&buf_long);
|
|
||||||
var w_short: std.Io.Writer = .fixed(&buf_short);
|
|
||||||
const cfg = stubConfig("/zhome", "/cache");
|
|
||||||
|
|
||||||
try run(std.testing.io, cfg, &[_][]const u8{"--verbose"}, &w_long);
|
|
||||||
try run(std.testing.io, cfg, &[_][]const u8{"-v"}, &w_short);
|
|
||||||
|
|
||||||
try std.testing.expectEqualStrings(w_long.buffered(), w_short.buffered());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "run: unknown flag returns UnexpectedArg and writes nothing to out" {
|
|
||||||
var buf: [1024]u8 = undefined;
|
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
|
||||||
const cfg = stubConfig(null, "/cache");
|
|
||||||
const args = [_][]const u8{"--bogus"};
|
|
||||||
|
|
||||||
try std.testing.expectError(error.UnexpectedArg, run(std.testing.io, cfg, &args, &w));
|
|
||||||
|
|
||||||
// The error path returns before any writing to `out`. Stderr output
|
|
||||||
// (the "unknown argument" line) goes through cli.stderrPrint directly
|
|
||||||
// and is not observable via the fixed writer.
|
|
||||||
try std.testing.expectEqual(@as(usize, 0), w.buffered().len);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "writeVersion: ZFIN_HOME=null renders '(unset)'" {
|
test "writeVersion: ZFIN_HOME=null renders '(unset)'" {
|
||||||
var buf: [2048]u8 = undefined;
|
var buf: [2048]u8 = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
|
|
|
||||||
11
src/main.zig
11
src/main.zig
|
|
@ -26,6 +26,7 @@ const command_modules = .{
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
.cache = @import("commands/cache.zig"),
|
.cache = @import("commands/cache.zig"),
|
||||||
|
.version = @import("commands/version.zig"),
|
||||||
};
|
};
|
||||||
|
|
||||||
comptime {
|
comptime {
|
||||||
|
|
@ -388,16 +389,6 @@ fn runCli(init: std.process.Init) !u8 {
|
||||||
var config = zfin.Config.fromEnv(io, allocator, init.environ_map);
|
var config = zfin.Config.fromEnv(io, allocator, init.environ_map);
|
||||||
defer config.deinit();
|
defer config.deinit();
|
||||||
|
|
||||||
// Version: doesn't need DataService; uses build_info + Config paths.
|
|
||||||
if (std.mem.eql(u8, command, "version")) {
|
|
||||||
commands.version.run(io, config, cmd_args, out) catch |err| switch (err) {
|
|
||||||
error.UnexpectedArg => return 1,
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
try out.flush();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var svc = zfin.DataService.init(io, allocator, config);
|
var svc = zfin.DataService.init(io, allocator, config);
|
||||||
defer svc.deinit();
|
defer svc.deinit();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue