Compare commits
No commits in common. "80d398f5404fb3ca9cbdc01858f9edb30bec4cac" and "c06ae87cbceec8cc3c2e263eedbc2495b60bfe3a" have entirely different histories.
80d398f540
...
c06ae87cbc
4 changed files with 56 additions and 67 deletions
|
|
@ -66,7 +66,7 @@ pub fn parseArgs(comptime T: type, allocator: Allocator) !T {
|
||||||
/// Do not pass the process name as an argument.
|
/// Do not pass the process name as an argument.
|
||||||
///
|
///
|
||||||
/// Parsing order of arguments is based on the order they are declared in `T`.
|
/// Parsing order of arguments is based on the order they are declared in `T`.
|
||||||
pub fn parseArgsFromSlice(comptime T: type, allocator: Allocator, args: [][:0]const u8) !T {
|
pub fn parseArgsFromSlice(comptime T: type, allocator: Allocator, args: [][]const u8) !T {
|
||||||
var flags = try std.ArrayList(Arg).initCapacity(allocator, 4);
|
var flags = try std.ArrayList(Arg).initCapacity(allocator, 4);
|
||||||
defer flags.deinit(allocator);
|
defer flags.deinit(allocator);
|
||||||
|
|
||||||
|
|
@ -383,7 +383,7 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
const t = std.testing;
|
const t = std.testing;
|
||||||
|
|
||||||
test "parse args" {
|
test "parse args" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 4);
|
const args = try t.allocator.alloc([]const u8, 4);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
|
|
@ -433,7 +433,7 @@ test "parse args" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing flag" {
|
test "missing flag" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 1);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "1234";
|
args[0] = "1234";
|
||||||
|
|
@ -461,7 +461,7 @@ test "missing flag" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing toggle" {
|
test "missing toggle" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 1);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "1234";
|
args[0] = "1234";
|
||||||
|
|
@ -489,7 +489,7 @@ test "missing toggle" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing positional because no args" {
|
test "missing positional because no args" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 0);
|
const args = try t.allocator.alloc([]const u8, 0);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
|
|
@ -511,7 +511,7 @@ test "missing positional because no args" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing positional because empty arg" {
|
test "missing positional because empty arg" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 1);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "";
|
args[0] = "";
|
||||||
|
|
@ -535,7 +535,7 @@ test "missing positional because empty arg" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "positional has default value so we get a free pass" {
|
test "positional has default value so we get a free pass" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 1);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "--toggle";
|
args[0] = "--toggle";
|
||||||
|
|
@ -562,7 +562,7 @@ test "positional has default value so we get a free pass" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse fn (positional)" {
|
test "parse fn (positional)" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 3);
|
const args = try t.allocator.alloc([]const u8, 3);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "1234";
|
args[0] = "1234";
|
||||||
|
|
@ -612,7 +612,7 @@ test "parse fn (positional)" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse fn (flag)" {
|
test "parse fn (flag)" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 3);
|
const args = try t.allocator.alloc([]const u8, 3);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "--number=1234";
|
args[0] = "--number=1234";
|
||||||
|
|
@ -671,7 +671,7 @@ test "parse fn (flag)" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse failure because no args" {
|
test "parse failure because no args" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 0);
|
const args = try t.allocator.alloc([]const u8, 0);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
|
|
@ -688,7 +688,7 @@ test "parse failure because no args" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "remainder has value" {
|
test "remainder has value" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 3);
|
const args = try t.allocator.alloc([]const u8, 3);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "--flag=value";
|
args[0] = "--flag=value";
|
||||||
|
|
@ -721,7 +721,7 @@ test "remainder has value" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "sub command from remainder" {
|
test "sub command from remainder" {
|
||||||
const args = try t.allocator.alloc([:0]const u8, 3);
|
const args = try t.allocator.alloc([]const u8, 3);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[0] = "--flag=value";
|
args[0] = "--flag=value";
|
||||||
|
|
@ -785,13 +785,7 @@ test "sub command from remainder" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const outerItems = outerResult.remainder.value.items;
|
var innerResult = try parseArgsFromSlice(DemoInner, t.allocator, outerResult.remainder.value.items);
|
||||||
|
|
||||||
var innerResult = try parseArgsFromSlice(
|
|
||||||
DemoInner,
|
|
||||||
t.allocator,
|
|
||||||
@ptrCast(outerItems),
|
|
||||||
);
|
|
||||||
defer innerResult.deinit();
|
defer innerResult.deinit();
|
||||||
|
|
||||||
try t.expectEqualStrings("value", innerResult.flag.value);
|
try t.expectEqualStrings("value", innerResult.flag.value);
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,19 @@ const Extra = arg_lib.Extra;
|
||||||
|
|
||||||
const niceTypeName = @import("../util/utils.zig").niceTypeName;
|
const niceTypeName = @import("../util/utils.zig").niceTypeName;
|
||||||
|
|
||||||
pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Writer) !void {
|
pub fn printHelp(comptime T: type, comptime name: []const u8, writer: std.io.AnyWriter) !void {
|
||||||
const info = @typeInfo(T);
|
const info = @typeInfo(T);
|
||||||
|
|
||||||
switch (info) {
|
switch (info) {
|
||||||
.@"struct" => {},
|
.Struct => {},
|
||||||
else => {
|
else => {
|
||||||
log.warn("We only support printing the help of structs, not {s}", .{@tagName(info)});
|
log.warn("We only support printing the help of `Struct`s, not {s}", .{@tagName(info)});
|
||||||
return error.NotImplemented;
|
return error.NotImplemented;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasFlags = false;
|
var hasFlags = false;
|
||||||
inline for (info.@"struct".fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, "allocator")) {
|
if (std.mem.eql(u8, field.name, "allocator")) {
|
||||||
comptime continue;
|
comptime continue;
|
||||||
}
|
}
|
||||||
|
|
@ -32,13 +32,13 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasPositionals = false;
|
var hasPositionals = false;
|
||||||
inline for (info.@"struct".fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, "allocator")) {
|
if (std.mem.eql(u8, field.name, "allocator")) {
|
||||||
comptime continue;
|
comptime continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueOpaque = field.default_value_ptr orelse @panic("Missing default value for field " ++ field.name);
|
const valueOpaque = field.default_value orelse @panic("Missing default value for field " ++ field.name);
|
||||||
const valueMarker: *const field.type = @ptrCast(@alignCast(valueOpaque));
|
const valueMarker: *const field.type = @alignCast(@ptrCast(valueOpaque));
|
||||||
const value: Extra = @field(valueMarker, "extra");
|
const value: Extra = @field(valueMarker, "extra");
|
||||||
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
|
|
@ -51,13 +51,13 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasRemainder = false;
|
var hasRemainder = false;
|
||||||
inline for (info.@"struct".fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, "allocator")) {
|
if (std.mem.eql(u8, field.name, "allocator")) {
|
||||||
comptime continue;
|
comptime continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueOpaque = field.default_value_ptr orelse @panic("Missing default value for field " ++ field.name);
|
const valueOpaque = field.default_value orelse @panic("Missing default value for field " ++ field.name);
|
||||||
const valueMarker: *const field.type = @ptrCast(@alignCast(valueOpaque));
|
const valueMarker: *const field.type = @alignCast(@ptrCast(valueOpaque));
|
||||||
const value: Extra = @field(valueMarker, "extra");
|
const value: Extra = @field(valueMarker, "extra");
|
||||||
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
|
|
@ -76,13 +76,13 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPositionals) {
|
if (hasPositionals) {
|
||||||
inline for (info.@"struct".fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, "allocator")) {
|
if (std.mem.eql(u8, field.name, "allocator")) {
|
||||||
comptime continue;
|
comptime continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueOpaque = field.default_value_ptr orelse @panic("Missing default value for field " ++ field.name);
|
const valueOpaque = field.default_value orelse @panic("Missing default value for field " ++ field.name);
|
||||||
const valueMarker: *const field.type = @ptrCast(@alignCast(valueOpaque));
|
const valueMarker: *const field.type = @alignCast(@ptrCast(valueOpaque));
|
||||||
const value: Extra = @field(valueMarker, "extra");
|
const value: Extra = @field(valueMarker, "extra");
|
||||||
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
|
|
@ -101,13 +101,13 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
try writer.print("\n", .{});
|
try writer.print("\n", .{});
|
||||||
try writer.print("Legend: <required> [optional]\n\n", .{});
|
try writer.print("Legend: <required> [optional]\n\n", .{});
|
||||||
|
|
||||||
inline for (info.@"struct".fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, "allocator")) {
|
if (std.mem.eql(u8, field.name, "allocator")) {
|
||||||
comptime continue;
|
comptime continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueOpaque = field.default_value_ptr orelse @panic("Missing default value for field " ++ field.name);
|
const valueOpaque = field.default_value orelse @panic("Missing default value for field " ++ field.name);
|
||||||
const valueMarker: *const field.type = @ptrCast(@alignCast(valueOpaque));
|
const valueMarker: *const field.type = @alignCast(@ptrCast(valueOpaque));
|
||||||
const valueType = niceTypeName(@TypeOf(valueMarker.*.value));
|
const valueType = niceTypeName(@TypeOf(valueMarker.*.value));
|
||||||
|
|
||||||
const isOptional = std.mem.startsWith(u8, valueType, "?");
|
const isOptional = std.mem.startsWith(u8, valueType, "?");
|
||||||
|
|
@ -122,7 +122,7 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
valueType,
|
valueType,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pos.type_hint) |typeHint| {
|
if (pos.typeHint) |typeHint| {
|
||||||
try writer.print(" ({s})", .{typeHint});
|
try writer.print(" ({s})", .{typeHint});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,24 +137,24 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
flag.name,
|
flag.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (flag.takes_value) {
|
if (flag.takesValue) {
|
||||||
try writer.print("=<value>", .{});
|
try writer.print("=<value>", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag.short) |short| {
|
if (flag.short) |short| {
|
||||||
try writer.print(" (-{s}", .{short});
|
try writer.print(" (-{s}", .{short});
|
||||||
|
|
||||||
if (flag.takes_value) {
|
if (flag.takesValue) {
|
||||||
try writer.print("=<value>", .{});
|
try writer.print("=<value>", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
try writer.print(")", .{});
|
try writer.print(")", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag.takes_value) {
|
if (flag.takesValue) {
|
||||||
try writer.print(": {s}", .{valueType});
|
try writer.print(": {s}", .{valueType});
|
||||||
|
|
||||||
if (flag.type_hint) |typeHint| {
|
if (flag.typeHint) |typeHint| {
|
||||||
try writer.print(" ({s})", .{typeHint});
|
try writer.print(" ({s})", .{typeHint});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,22 +175,25 @@ pub fn printHelp(comptime T: type, comptime name: []const u8, writer: *std.Io.Wr
|
||||||
|
|
||||||
const t = std.testing;
|
const t = std.testing;
|
||||||
test "empty help" {
|
test "empty help" {
|
||||||
const Demo = struct {};
|
var buf = std.ArrayList(u8).init(t.allocator);
|
||||||
|
|
||||||
var buf = std.Io.Writer.Allocating.init(t.allocator);
|
|
||||||
defer buf.deinit();
|
defer buf.deinit();
|
||||||
|
|
||||||
try printHelp(Demo, "demo", &buf.writer);
|
const Demo = struct {};
|
||||||
|
|
||||||
|
try printHelp(Demo, "demo", buf.writer().any());
|
||||||
|
|
||||||
try t.expectEqualStrings(
|
try t.expectEqualStrings(
|
||||||
\\Usage: demo
|
\\Usage: demo
|
||||||
\\Legend: <required> [optional]
|
\\Legend: <required> [optional]
|
||||||
\\
|
\\
|
||||||
\\
|
\\
|
||||||
, buf.written());
|
, buf.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "basic help" {
|
test "basic help" {
|
||||||
|
var buf = std.ArrayList(u8).init(t.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
verbose: Marker(bool) = .{
|
verbose: Marker(bool) = .{
|
||||||
.value = undefined,
|
.value = undefined,
|
||||||
|
|
@ -214,14 +217,7 @@ test "basic help" {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var buf = std.Io.Writer.Allocating.init(t.allocator);
|
try printHelp(Demo, "demo", buf.writer().any());
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
try printHelp(
|
|
||||||
Demo,
|
|
||||||
"demo",
|
|
||||||
&buf.writer,
|
|
||||||
);
|
|
||||||
|
|
||||||
try t.expectEqualStrings(
|
try t.expectEqualStrings(
|
||||||
\\Usage: demo [flags] <positional> [...]
|
\\Usage: demo [flags] <positional> [...]
|
||||||
|
|
@ -230,32 +226,28 @@ test "basic help" {
|
||||||
\\* --verbose (-v)
|
\\* --verbose (-v)
|
||||||
\\* <positional>: string
|
\\* <positional>: string
|
||||||
\\
|
\\
|
||||||
, buf.written());
|
, buf.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "about and type hint" {
|
test "about and type hint" {
|
||||||
|
var buf = std.ArrayList(u8).init(t.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
verbose: Marker(bool) = .{
|
verbose: Marker(bool) = .{
|
||||||
.value = undefined,
|
.value = undefined,
|
||||||
.extra = .{ .Flag = .{
|
.extra = .{ .Flag = .{
|
||||||
.name = "verbose",
|
.name = "verbose",
|
||||||
.short = "v",
|
.short = "v",
|
||||||
.takes_value = true,
|
.takesValue = true,
|
||||||
|
|
||||||
.about = "makes the output verbose",
|
.about = "makes the output verbose",
|
||||||
.type_hint = "yes/no",
|
.typeHint = "yes/no",
|
||||||
} },
|
} },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var buf = std.Io.Writer.Allocating.init(t.allocator);
|
try printHelp(Demo, "demo", buf.writer().any());
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
try printHelp(
|
|
||||||
Demo,
|
|
||||||
"demo",
|
|
||||||
&buf.writer,
|
|
||||||
);
|
|
||||||
|
|
||||||
try t.expectEqualStrings(
|
try t.expectEqualStrings(
|
||||||
\\Usage: demo [flags]
|
\\Usage: demo [flags]
|
||||||
|
|
@ -263,5 +255,5 @@ test "about and type hint" {
|
||||||
\\
|
\\
|
||||||
\\* --verbose=<value> (-v=<value>): bool (yes/no) <required> | makes the output verbose
|
\\* --verbose=<value> (-v=<value>): bool (yes/no) <required> | makes the output verbose
|
||||||
\\
|
\\
|
||||||
, buf.written());
|
, buf.items);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ comptime {
|
||||||
|
|
||||||
if (builtin.is_test) {
|
if (builtin.is_test) {
|
||||||
std.mem.doNotOptimizeAway(args);
|
std.mem.doNotOptimizeAway(args);
|
||||||
std.mem.doNotOptimizeAway(args.help);
|
|
||||||
std.mem.doNotOptimizeAway(args.parsers);
|
|
||||||
|
|
||||||
std.mem.doNotOptimizeAway(log);
|
std.mem.doNotOptimizeAway(log);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ pub fn niceTypeName(comptime T: type) []const u8 {
|
||||||
|
|
||||||
const name = @typeName(T);
|
const name = @typeName(T);
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, name, "array_list.Aligned")) {
|
||||||
|
return "array";
|
||||||
|
}
|
||||||
|
|
||||||
if (std.mem.lastIndexOf(u8, name, ".")) |idx| {
|
if (std.mem.lastIndexOf(u8, name, ".")) |idx| {
|
||||||
return name[idx + 1 ..];
|
return name[idx + 1 ..];
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +27,7 @@ test "nice type names" {
|
||||||
};
|
};
|
||||||
|
|
||||||
try t.expectEqualStrings("string", niceTypeName([]const u8));
|
try t.expectEqualStrings("string", niceTypeName([]const u8));
|
||||||
|
try t.expectEqualStrings("array", niceTypeName(std.ArrayList(u8)));
|
||||||
try t.expectEqualStrings("Enum", niceTypeName(Enum));
|
try t.expectEqualStrings("Enum", niceTypeName(Enum));
|
||||||
|
|
||||||
try t.expectEqualStrings("u8", niceTypeName(u8));
|
try t.expectEqualStrings("u8", niceTypeName(u8));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue