Hello! I also got this implemented in my library! In essence, when you parse a command you can treat it as a tagged union, and according to what the first command is, change what the parser should look for.
The thing you have to implement first is to detect how many different command the user can enter, and then make them into a tagged union. Like this structure from this example
const def = .{
.flags = .{ Flag("verbose", "v", "Print more" ) },
.commands = .{
.entry = .{
.commands = .{
.start = entry_start,
.status = .{},
.stop = .{},
}
},
.project = .{
.commands = .{
.create = project_create,
.rename = project_rename,
}
},
}
};
becomes this enum with some comptime magic
const CommandUnion = enum {
entry,
project,
};
and i do it with a code like this in src/gnu_parse.zig, line 146
const CommandUnion = comptime find_cmd: {
const fields = @typeInfo(ReArgs).@"struct".fields;
for (fields) |f| {
if (std.mem.eql(u8, f.name, "cmd")) {
break :find_cmd f.type;
}
}
unreachable; // if @hasField(definition, "commands") this is unreachable
};
Of course this is inside a function which explores the user input, and then changes what shoud it parse according to the union the command took. The idea is that inside of a subcommand you have the same problem you were solving up until now, which you just call the same function again:
- if next argument is a command, you recall the function with the remining commands
- if its an argument, a flag or an option, you call your actual parsing logic, as you know there will be no more commands (at least this is what i assume)
This makes the code the user has to write like the following one, from the same example:
switch (arguments.cmd) {
.entry => |entry_cmd| {
switch (entry_cmd.cmd) {
.start => |start_args| {
try stdout.print("'entry start' detected!\n", .{});
if (start_args.projectid) |pid| {
try stdout.print("Detected pid: {d}\n", .{pid});
} else {
try stdout.writeAll("No pid detected\n");
}
},
.stop => try stdout.writeAll("'entry stop' detected!\n"),
.status => try stdout.writeAll("'entry status' detected!\n"),
}
},
.project => |project_cmd| {
switch (project_cmd.cmd) {
.create => |create_args| {
try stdout.print("Creating project: {s}\n", .{create_args.description});
},
.rename => |rename_args| {
try stdout.print("Renaming ID {d} to {s}\n", .{rename_args.projectid, rename_args.name});
}
}
}
}
Idk if you are taking advantage of comptime execution to generate code that parses the exact user definition. If you are taking a more traditional approach, maybe this is not what you were looking for and I am sorry haha