This is an interesting change. While I think generally passing in constant userdata is not terribly useful, the previous implementation precluded it entirely. Interface types, for example, are often passed directly and stored as constants (they hold pointers to their mutable state). Since we type erase this so it can be bound to the generic interface object, non-pointer objects must be passed by reference to avoid binding the parser interface to a temporary stack copy of the object. This means we have to handle these cases slightly differently. Also, while technically being classified as pointers, slices don't really behave like pointers, which is understandable but annoying. There's a bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder (u32) both require an *u32 when binding the parser interface. This is of course because pointers do not need to be rewrapped to be type erased. The same code path could be used for both cases, but then the user would have to pass in a pointer to a pointer, which actually looks a bit silly because then it potentially means having to do &&my_var.
120 lines
3.7 KiB
Zig
120 lines
3.7 KiB
Zig
const std = @import("std");
|
|
const noclip = @import("noclip");
|
|
|
|
const CommandBuilder = noclip.CommandBuilder;
|
|
|
|
const Choice = enum { first, second };
|
|
|
|
const cli = cmd: {
|
|
var cmd = CommandBuilder(*u32){
|
|
.description =
|
|
\\The definitive noclip demonstration utility
|
|
\\
|
|
\\This command demonstrates the functionality of the noclip library. cool!
|
|
,
|
|
};
|
|
cmd.add_option(.{ .OutputType = struct { u8, u8 } }, .{
|
|
.name = "test",
|
|
.short_tag = "-t",
|
|
.long_tag = "--test",
|
|
.env_var = "NOCLIP_TEST",
|
|
.description = "multi-value test option",
|
|
.nice_type_name = "int> <int",
|
|
});
|
|
cmd.add_option(.{ .OutputType = Choice }, .{
|
|
.name = "choice",
|
|
.short_tag = "-c",
|
|
.long_tag = "--choice",
|
|
.default = .second,
|
|
.env_var = "NOCLIP_CHOICE",
|
|
.description = "enum choice option",
|
|
.nice_type_name = "choice",
|
|
});
|
|
cmd.add_option(.{ .OutputType = u32 }, .{
|
|
.name = "default",
|
|
.short_tag = "-d",
|
|
.long_tag = "--default",
|
|
.env_var = "NOCLIP_DEFAULT",
|
|
.default = 100,
|
|
.description = "default value integer option",
|
|
.nice_type_name = "uint",
|
|
});
|
|
cmd.add_option(.{ .OutputType = u8, .multi = true }, .{
|
|
.name = "multi",
|
|
.short_tag = "-m",
|
|
.long_tag = "--multi",
|
|
.description = "multiple specification test option",
|
|
});
|
|
cmd.add_flag(.{}, .{
|
|
.name = "flag",
|
|
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
|
|
.falsy = .{ .short_tag = "-F", .long_tag = "--no-flag" },
|
|
.env_var = "NOCLIP_FLAG",
|
|
.description = "boolean flag",
|
|
});
|
|
cmd.add_flag(.{ .multi = true }, .{
|
|
.name = "multiflag",
|
|
.truthy = .{ .short_tag = "-M" },
|
|
.description = "multiple specification test flag ",
|
|
});
|
|
cmd.add_option(.{ .OutputType = u8 }, .{
|
|
.name = "env",
|
|
.env_var = "NOCLIP_ENVIRON",
|
|
.description = "environment variable only option",
|
|
});
|
|
cmd.add_argument(.{ .OutputType = []const u8 }, .{
|
|
.name = "arg",
|
|
.description = "This is an argument that doesn't really do anything, but it's very important.",
|
|
});
|
|
|
|
break :cmd cmd;
|
|
};
|
|
|
|
const subcommand = cmd: {
|
|
var cmd = CommandBuilder([]const u8){
|
|
.description =
|
|
\\Perform some sort of work
|
|
\\
|
|
\\This subcommand is a mystery. It probably does something, but nobody is sure what.
|
|
,
|
|
};
|
|
cmd.simple_flag(.{
|
|
.name = "flag",
|
|
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
|
|
.falsy = .{ .long_tag = "--no-flag" },
|
|
.env_var = "NOCLIP_SUBFLAG",
|
|
});
|
|
cmd.add_argument(.{ .OutputType = []const u8 }, .{ .name = "argument" });
|
|
break :cmd cmd;
|
|
};
|
|
|
|
fn sub_handler(context: []const u8, result: subcommand.Output()) !void {
|
|
std.debug.print("subcommand: {s}\n", .{result.argument});
|
|
std.debug.print("context: {s}\n", .{context});
|
|
}
|
|
|
|
fn cli_handler(context: *u32, result: cli.Output()) !void {
|
|
std.debug.print("context: {d}\n", .{context.*});
|
|
std.debug.print("callback is working {any}\n", .{result.choice});
|
|
std.debug.print("callback is working {d}\n", .{result.default});
|
|
context.* += 1;
|
|
}
|
|
|
|
pub fn main() !u8 {
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
var parser = cli.create_parser(cli_handler, allocator);
|
|
var context: u32 = 2;
|
|
const sc: []const u8 = "whassup";
|
|
|
|
var subcon = subcommand.create_parser(sub_handler, allocator);
|
|
try parser.add_subcommand("verb", subcon.interface(&sc));
|
|
|
|
const iface = parser.interface(&context);
|
|
iface.execute() catch return 1;
|
|
|
|
return 0;
|
|
}
|