Hello again!
I am very satisfied to say that I’ve made some progress regarding this, and despite needing still a bit more love, it is actually done! Now command parsing has been added like this in a simplified git example.
const gitu_def = .{
// set global arguments for the whole program
.flags = .{ Flag("verbose", "v", "Enable verbose logging") },
.optional = .{ Opt([]const u8, "config", "c", "~/.gituconfig", "Path to config file") },
.commands = .{
.init = .{ // simple command with 1 positional argument
.required = .{ Arg([]const u8, "path", "Where to create the repository") },
.flags = .{ Flag("bare", "b", "Create a bare repository") },
},
.commit = .{ // just options and flags
.optional = .{ Opt([]const u8, "message", "m", "Default Message", "Commit message") },
.flags = .{ Flag("amend", "a", "Amend the previous commit") },
},
.remote = .{
.commands = .{ // nested subcommands !
.add = .{
.required = .{ // multiple required args // gitu remote add <name> <url>
Arg([]const u8, "name", "Remote name (e.g. origin)"),
Arg([]const u8, "url", "Remote URL"),
},
.optional = .{ Opt([]const u8, "track", "t", "master", "Branch to track") },
},
.show = .{
.required = .{ Arg([]const u8, "name", "Remote name to inspect") },
},
},
},
},
};
This definition complies with the following rules:
- No
.commands and .required at the same level.
- No
.commands can appear if a .required has already appeared.
- Every label must contain just it’s adequate type (eg, in
.required you just can put Arg)
Now, regards to the parsing I had several meltdowns, and I essentially implemented two functions after messing arround with “how can you validly parse the arguments”. I ended up with two implementations, a freestyle no rules like all GNU linux utilities which requires an allocator. This will parse all the variations in flag and options positioning, like gitu init -v -b "path", gitu init -v "path" -b as well as gitu -v init "path" -b (at least I hope)
//convert the args into a slice
const args = try init.minimal.args.toSlice(init.arena.allocator());
const arguments = argz.parseArgs(init.gpa, gitu_def, args, stdout, stderr) catch |err| {
switch (err) {
ParseErrors.HelpShown => try stdout.flush(),
else => try stderr.flush(),
}
std.process.exit(0);
};
The other implementation is a POSIX “compliant” (I have to actually check what posix compliant really means from the source to test how compliant it is), which means the following order always: Flags/Options, required for every level. That means that the only valid parsing of the above example is gitu -v init -b "here": the flag is defied globally so must be just after the program name, and -b just after the command. In exchange for rigidity, this just iterates once per all the arguments, so it’s much faster (not that the difference is noticable but you know)
// also, you can do it strict posix
var iter = init.minimal.args.iterate();
const arguments = argz.parseArgsPosix(gitu_def, &iter, stdout, stderr) catch |err| {
switch (err) {
ParseErrors.HelpShown => try stdout.flush(),
else => try stderr.flush(),
}
std.process.exit(0);
};
An indefinite number of subcommands can be nested also, and they are reificated as TaggedUnions with the Enums being the names on the .command tuples. This part specifically has been written with lots of sweat and tears with inlineand comptime shenaningans, but I am confident that it works 
Lastly, the help message is not done and the POSIX implementation has to be tested and validated, but if you want to play around and break it I would be very happy!
I also wanted to ask a question. Do you think it’s better to shorten the names to three letters?
.required → .req
.optional → opt
.flag → flg
.commands → cmd
It would made the definition shorter but I am not sure.
Thank you for reading this and have a nice day!!