milestone 15: make a green run mean a real pass
This commit is contained in:
+57
-9
@@ -73,6 +73,29 @@ pub const Command = union(enum) {
|
||||
help,
|
||||
};
|
||||
|
||||
/// The argv spelling of a command paired with its tag. The two differ for
|
||||
/// `export` and `import`, whose tags carry a trailing underscore because both
|
||||
/// words are Zig keywords.
|
||||
pub const CommandName = struct { name: []const u8, tag: std.meta.Tag(Command) };
|
||||
|
||||
/// The one list of subcommands. `parseArgs` matches the command word against
|
||||
/// it, `docs_drift_test.zig` derives its reference-heading needles from it, and
|
||||
/// a test below holds `usage_text` to it.
|
||||
pub const command_names = [_]CommandName{
|
||||
.{ .name = "run", .tag = .run },
|
||||
.{ .name = "check", .tag = .check },
|
||||
.{ .name = "export", .tag = .export_ },
|
||||
.{ .name = "import", .tag = .import_ },
|
||||
.{ .name = "version", .tag = .version },
|
||||
.{ .name = "help", .tag = .help },
|
||||
};
|
||||
|
||||
comptime {
|
||||
// A tag added to `Command` without an entry here fails the compile rather
|
||||
// than silently dropping out of the parser and the doc guard.
|
||||
std.debug.assert(command_names.len == @typeInfo(Command).@"union".fields.len);
|
||||
}
|
||||
|
||||
pub const ParseError = error{
|
||||
UnknownCommand,
|
||||
UnknownFlag,
|
||||
@@ -88,18 +111,30 @@ pub fn parseArgs(argv: []const []const u8) ParseError!Command {
|
||||
const command = argv[0];
|
||||
const rest = argv[1..];
|
||||
|
||||
if (eql(command, "version")) {
|
||||
if (rest.len != 0) return error.TooManyArguments;
|
||||
return .version;
|
||||
}
|
||||
if (eql(command, "help") or eql(command, "--help") or eql(command, "-h")) {
|
||||
// The two flag spellings of `help` are not subcommands, so they are not in
|
||||
// `command_names` and are matched before it.
|
||||
if (eql(command, "--help") or eql(command, "-h")) {
|
||||
if (rest.len != 0) return error.TooManyArguments;
|
||||
return .help;
|
||||
}
|
||||
if (eql(command, "run")) return .{ .run = try parseRunArgs(rest) };
|
||||
if (eql(command, "check")) return .{ .check = try parseCheckArgs(rest) };
|
||||
if (eql(command, "export")) return .{ .export_ = try parseExportArgs(rest) };
|
||||
if (eql(command, "import")) return .{ .import_ = try parseImportArgs(rest) };
|
||||
|
||||
for (command_names) |entry| {
|
||||
if (!eql(command, entry.name)) continue;
|
||||
switch (entry.tag) {
|
||||
.run => return .{ .run = try parseRunArgs(rest) },
|
||||
.check => return .{ .check = try parseCheckArgs(rest) },
|
||||
.export_ => return .{ .export_ = try parseExportArgs(rest) },
|
||||
.import_ => return .{ .import_ = try parseImportArgs(rest) },
|
||||
.version => {
|
||||
if (rest.len != 0) return error.TooManyArguments;
|
||||
return .version;
|
||||
},
|
||||
.help => {
|
||||
if (rest.len != 0) return error.TooManyArguments;
|
||||
return .help;
|
||||
},
|
||||
}
|
||||
}
|
||||
return error.UnknownCommand;
|
||||
}
|
||||
|
||||
@@ -1076,6 +1111,19 @@ test "parseArgs rejects an extra positional argument" {
|
||||
try testing.expectError(error.TooManyArguments, parseArgs(&.{ "-h", "extra" }));
|
||||
}
|
||||
|
||||
test "usage_text lists every command in command_names" {
|
||||
// The commands block indents each entry by two spaces, so a command that
|
||||
// survives only as a word inside an option description does not count.
|
||||
for (command_names) |entry| {
|
||||
var needle_buf: [32]u8 = undefined;
|
||||
const needle = try std.fmt.bufPrint(&needle_buf, "\n {s} ", .{entry.name});
|
||||
if (std.mem.indexOf(u8, usage_text, needle) == null) {
|
||||
std.debug.print("command missing from usage_text: {s}\n", .{entry.name});
|
||||
return error.CommandMissingFromUsage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "usage writes non-empty text" {
|
||||
var out: Writer.Allocating = .init(testing.allocator);
|
||||
defer out.deinit();
|
||||
|
||||
Reference in New Issue
Block a user