Lots of minor improvements and logging
This commit is contained in:
parent
6e9d0197de
commit
e52d3c3915
1 changed files with 158 additions and 48 deletions
|
|
@ -44,12 +44,11 @@ pub fn parseArgs(comptime T: type, allocator: Allocator) !T {
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
defer std.process.argsFree(allocator, args);
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
return parseArgsImpl(T, allocator, args);
|
return parseArgsFromSlice(T, allocator, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// actual implementation of parse_args, necessary for testing purposes.
|
/// All items of `args` must be valid, otherwise you will get a General Protection Fault.
|
||||||
/// The main implementation is in parse_args, which is a wrapper around this.
|
pub fn parseArgsFromSlice(comptime T: type, allocator: Allocator, args: [][]const u8) !T {
|
||||||
fn parseArgsImpl(comptime T: type, allocator: Allocator, args: [][]const u8) !T {
|
|
||||||
var flags = std.ArrayList(Arg).init(allocator);
|
var flags = std.ArrayList(Arg).init(allocator);
|
||||||
defer flags.deinit();
|
defer flags.deinit();
|
||||||
|
|
||||||
|
|
@ -59,6 +58,8 @@ fn parseArgsImpl(comptime T: type, allocator: Allocator, args: [][]const u8) !T
|
||||||
|
|
||||||
for (args, 0..) |arg, idx| {
|
for (args, 0..) |arg, idx| {
|
||||||
if (idx == 0) continue; // skip first arg: process name
|
if (idx == 0) continue; // skip first arg: process name
|
||||||
|
if (arg.len == 0) continue; // skip empty args
|
||||||
|
if (@intFromPtr(arg.ptr) == 0xaaaaaaaaaaaaaaaa) return error.UninitializedArgument;
|
||||||
|
|
||||||
const argument = try Arg.parse_arg(arg);
|
const argument = try Arg.parse_arg(arg);
|
||||||
|
|
||||||
|
|
@ -154,8 +155,8 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
switch (extra) {
|
switch (extra) {
|
||||||
.Flag => |f| {
|
.Flag => |f| {
|
||||||
if (!(f.takesValue or f.toggle)) {
|
if (!(f.takesValue or f.toggle)) {
|
||||||
log.err("invalid flag `{s}`: is not a toggle and doesn't take a value", .{f.name});
|
log.err("flag `{s}` must be a toggle or take a value, but it was neither", .{f.name});
|
||||||
return error.InvalidFlag;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maybe_flag = find(flags, f.name, f.short);
|
const maybe_flag = find(flags, f.name, f.short);
|
||||||
|
|
@ -167,11 +168,11 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
fie.*.value = true;
|
fie.*.value = true;
|
||||||
comptime continue;
|
comptime continue;
|
||||||
} else {
|
} else {
|
||||||
log.err("invalid switch `{s}`: expected T == bool, found: {s}", .{
|
log.err("invalid toggle `{s}`: expected T == bool, found: {s}", .{
|
||||||
f.name,
|
f.name,
|
||||||
@typeName(@TypeOf(fie.*.value)),
|
niceTypeName(@TypeOf(fie.*.value)),
|
||||||
});
|
});
|
||||||
return error.InvalidSwitch;
|
return error.InvalidToggle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,14 +189,14 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
@errorName(err),
|
@errorName(err),
|
||||||
});
|
});
|
||||||
log.err("- tried to parse: {s}", .{value});
|
log.err("- tried to parse: {s}", .{value});
|
||||||
log.err("- expected type: {s}", .{@typeName(@TypeOf(fie.*.value))});
|
log.err("- expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
||||||
return error.InvalidFlag;
|
return error.InvalidFlag;
|
||||||
};
|
};
|
||||||
comptime continue;
|
comptime continue;
|
||||||
} else {
|
} else {
|
||||||
log.err("flag `{s}` expected a value, but no parser was provided for type `{s}`", .{
|
log.err("flag `{s}` expected a value, but no parser was provided for type `{s}`", .{
|
||||||
f.name,
|
f.name,
|
||||||
@typeName(@TypeOf(fie.*.value)),
|
niceTypeName(@TypeOf(fie.*.value)),
|
||||||
});
|
});
|
||||||
return error.NoValueForFlag;
|
return error.NoValueForFlag;
|
||||||
}
|
}
|
||||||
|
|
@ -210,34 +211,51 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
|
|
||||||
log.err("invalid flag `{s}`: is not a toggle and doesn't take a value", .{f.name});
|
log.err("invalid flag `{s}`: is not a toggle and doesn't take a value", .{f.name});
|
||||||
return error.InvalidFlag;
|
return error.InvalidFlag;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
const fieldType = @typeInfo(@TypeOf(fie.*.value));
|
const fieldType = @typeInfo(@TypeOf(fie.*.value));
|
||||||
|
|
||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
.Optional => |_| {
|
.Optional => |_| {
|
||||||
log.debug("flag `{s}` is optional, and we couldn't find a value for it", .{f.name});
|
log.debug("flag `{s}` is optional, and we couldn't find a value for it, so leaving as default value", .{f.name});
|
||||||
fie.*.value = null;
|
|
||||||
comptime continue;
|
comptime continue;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
|
if (f.toggle) {
|
||||||
|
if (@TypeOf(fie.*.value) == bool) {
|
||||||
|
fie.*.value = false;
|
||||||
|
comptime continue;
|
||||||
|
} else {
|
||||||
|
log.err("invalid toggle `{s}`: expected T == bool, found: {s}", .{
|
||||||
|
f.name,
|
||||||
|
niceTypeName(@TypeOf(fie.*.value)),
|
||||||
|
});
|
||||||
|
return error.InvalidToggle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (inDebugMode) {
|
if (inDebugMode) {
|
||||||
log.warn("flag `{s}` expected a value, but none was provided", .{f.name});
|
log.warn("flag `{s}` expected a value, but none was provided", .{f.name});
|
||||||
log.warn("expected type: {s}", .{@typeName(@TypeOf(fie.*.value))});
|
log.warn("expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
||||||
log.warn("but the flag wasn't provided...", .{});
|
|
||||||
} else {
|
} else {
|
||||||
log.err("flag `{s}` is required (expected type `{s}`)", .{
|
log.err("flag `{s}` is required (expected type `{s}`)", .{
|
||||||
f.name,
|
f.name,
|
||||||
@typeName(@TypeOf(fie.*.value)),
|
niceTypeName(@TypeOf(fie.*.value)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.warn("hint: try `--{s}=<value>`", .{f.name});
|
||||||
|
if (f.short) |short| {
|
||||||
|
log.warn("hint: try `-{s}=<value>`", .{short});
|
||||||
|
}
|
||||||
|
|
||||||
return error.NoValueForFlag;
|
return error.NoValueForFlag;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
log.err("invalid flag `{s}`: could not be located in args", .{f.name});
|
log.err("invalid flag `{s}`: could not be located in args", .{f.name});
|
||||||
return error.CouldNotFindFlag;
|
return error.CouldNotFindFlag;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
.Remainder => {
|
.Remainder => {
|
||||||
var not_consumed = std.ArrayList([]const u8).init(result.allocator);
|
var not_consumed = std.ArrayList([]const u8).init(result.allocator);
|
||||||
|
|
@ -265,7 +283,7 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
} else {
|
} else {
|
||||||
log.err("invalid remainder field `{s}`: expected T == `std.ArrayList([] const u8)`, got T == `{s}`", .{
|
log.err("invalid remainder field `{s}`: expected T == `std.ArrayList([] const u8)`, got T == `{s}`", .{
|
||||||
field.name,
|
field.name,
|
||||||
@typeName(@TypeOf(fie.*.value)),
|
niceTypeName(@TypeOf(fie.*.value)),
|
||||||
});
|
});
|
||||||
return error.InvalidRemainder;
|
return error.InvalidRemainder;
|
||||||
}
|
}
|
||||||
|
|
@ -284,15 +302,22 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
@errorName(err),
|
@errorName(err),
|
||||||
});
|
});
|
||||||
log.err("- tried to parse: {s}", .{flag.*.Positional.value});
|
log.err("- tried to parse: {s}", .{flag.*.Positional.value});
|
||||||
log.err("- expected type: {s}", .{@typeName(@TypeOf(fie.*.value))});
|
log.err("- expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
||||||
return error.InvalidPositional;
|
return error.InvalidPositional;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
log.err("could not parse positional argument for `{s}`: no parser provided", .{field.name});
|
log.err("could not parse positional argument for `{s}`: no parser provided", .{field.name});
|
||||||
return error.InvalidPositional;
|
return error.InvalidPositional;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (builtin.is_test) {
|
||||||
|
log.warn("could not find positional argument for `{s}`", .{field.name});
|
||||||
} else {
|
} else {
|
||||||
log.err("could not find positional argument for `{s}`", .{field.name});
|
log.err("could not find positional argument for `{s}`", .{field.name});
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("hint: expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
||||||
|
log.warn("hint: try `<value>`", .{});
|
||||||
return error.NoArgumentFound;
|
return error.NoArgumentFound;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -302,6 +327,14 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn niceTypeName(comptime T: type) []const u8 {
|
||||||
|
if (T == []const u8) {
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
return @typeName(T);
|
||||||
|
}
|
||||||
|
|
||||||
const t = std.testing;
|
const t = std.testing;
|
||||||
|
|
||||||
test "parse args" {
|
test "parse args" {
|
||||||
|
|
@ -349,7 +382,7 @@ test "parse args" {
|
||||||
args[2] = "--toggle";
|
args[2] = "--toggle";
|
||||||
args[3] = "positional";
|
args[3] = "positional";
|
||||||
|
|
||||||
var result = try parseArgsImpl(Demo, t.allocator, args);
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
try t.expectEqualStrings("value", result.flag.value);
|
try t.expectEqualStrings("value", result.flag.value);
|
||||||
|
|
@ -371,7 +404,7 @@ test "missing flag" {
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "flag",
|
.name = "flag",
|
||||||
.toggle = true,
|
.takesValue = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.parse = parsers.boolean,
|
.parse = parsers.boolean,
|
||||||
|
|
@ -382,10 +415,87 @@ test "missing flag" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = parseArgsImpl(Demo, t.allocator, args);
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
try t.expectError(error.NoValueForFlag, result);
|
try t.expectError(error.NoValueForFlag, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "missing toggle" {
|
||||||
|
const args = try t.allocator.alloc([]const u8, 2);
|
||||||
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
|
args[1] = "1234";
|
||||||
|
|
||||||
|
const Demo = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
flag: Marker(bool) = .{
|
||||||
|
.value = undefined,
|
||||||
|
.extra = Extra{
|
||||||
|
.Flag = .{
|
||||||
|
.name = "flag",
|
||||||
|
.toggle = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fn deinit(self: *@This()) void {
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = try parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
|
|
||||||
|
try t.expect(result.flag.value == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "missing positional because no args" {
|
||||||
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
|
const Demo = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
positional: Marker([]const u8) = .{
|
||||||
|
.value = undefined,
|
||||||
|
.extra = Extra{
|
||||||
|
.Positional = {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fn deinit(self: *@This()) void {
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
|
try t.expectError(error.NoArguments, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "missing positional because empty arg" {
|
||||||
|
const args = try t.allocator.alloc([]const u8, 2);
|
||||||
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
|
args[1] = "";
|
||||||
|
|
||||||
|
const Demo = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
positional: Marker([]const u8) = .{
|
||||||
|
.value = undefined,
|
||||||
|
.extra = Extra{
|
||||||
|
.Positional = {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fn deinit(self: *@This()) void {
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
|
try t.expectError(error.NoArgumentFound, result);
|
||||||
|
}
|
||||||
|
|
||||||
test "parse fn (positional)" {
|
test "parse fn (positional)" {
|
||||||
const args = try t.allocator.alloc([]const u8, 4);
|
const args = try t.allocator.alloc([]const u8, 4);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
@ -431,7 +541,7 @@ test "parse fn (positional)" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = try parseArgsImpl(Demo, t.allocator, args);
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
try t.expectEqual(1234, result.flag.value);
|
try t.expectEqual(1234, result.flag.value);
|
||||||
|
|
@ -471,7 +581,7 @@ test "parse fn (flag)" {
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "boolean",
|
.name = "boolean",
|
||||||
.takesValue = true,
|
.toggle = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.parse = parsers.boolean,
|
.parse = parsers.boolean,
|
||||||
|
|
@ -493,7 +603,7 @@ test "parse fn (flag)" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = try parseArgsImpl(Demo, t.allocator, args);
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
try t.expectEqual(1234, result.number.value);
|
try t.expectEqual(1234, result.number.value);
|
||||||
|
|
@ -515,7 +625,7 @@ test "parse failure because no args" {
|
||||||
|
|
||||||
args[0] = "zither";
|
args[0] = "zither";
|
||||||
|
|
||||||
const result = parseArgsImpl(Demo, t.allocator, args);
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
|
|
||||||
try t.expectError(error.NoArguments, result);
|
try t.expectError(error.NoArguments, result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue