Interested in your feedback on this approach to providing options for a relatively simple task: a command line argument parser. Rather than using a struct with typed fields, I realised that the minimal goals of the library could be achieved by using the comptime types of the initialisation data to understand their meaning:
.{ .{"output"}, .{"verbose", false}, .{"extract", 'x', false}, },
specifies three command line options. The first takes an argument, the second does not. It will recognise “–output filename” or “-o filename”, etc. The third takes no arguments and will recognise “–extract” or “-x”. And so on.
The argument order doesn’t matter, since there’s an inline for
loop that looks to the type to figure out the intent.
Now, I fully realise this is not well suited for code that’s going to expand with more features. For instance, if the library wants to support error handling, help/usage reporting, required fields, etc., this approach probably wouldn’t scale. But there are several good full-featured libraries already, so that’s not my goal.
The comptime processing was super-simple. There’s a guard at the top of the function:
const ti = @typeInfo(@TypeOf(options));
if (ti != .Struct or !ti.Struct.is_tuple) return error.BadArgument;
And a loop to extract the arguments and put them into a struct for further validation:
inline for (one) |x| {
const x_ty = @TypeOf(x);
const x_ti = @typeInfo(x_ty);
if (x_ty == comptime_int) {
const c: u8 = x;
if (c == 0) {
create_opts.short = null;
} else {
create_opts.short = c;
}
continue;
}
if (x_ty == bool) {
create_opts.boolean = true;
continue;
}
if (x_ty == []const u8) {
create_opts.name = x;
continue;
}
if (x_ti == .Pointer) {
if (@typeInfo(x_ti.Pointer.child) == .Array) {
create_opts.name = x;
continue;
}
}
// std.debug.print("Expected a slice, bool or comptime_int, got: {}\n", .{@TypeOf(x)});
return error.BadArgument;
}
It was really pleasing to be able to do a compile-time type check with a simple statement like if (x_ty == []const u8)
. However, it was a bit of trial and error to try to understand how to detect a zero-sentinel array, especially since the size is part of the type:
if (x_ti == .Pointer) {
if (@typeInfo(x_ti.Pointer.child) == .Array) {
create_opts.name = x;
continue;
}
}
so I’m wondering if there’s something even simpler I’ve missed in the standard library. Or, more generally, if I’m doing anything strange. Aside from the simple, non-scalable approach, I mean.
For reference, the whole function is linked below. Thank you for any feedback!