From c3b31b227415bf164b23fc90a4e28e51a010e4b4 Mon Sep 17 00:00:00 2001 From: torque Date: Sat, 1 Apr 2023 13:04:02 -0700 Subject: [PATCH] command/parser: sketch out help flag integration This is a special case flag that cannot be replicated with the normal machinery. It's much easier to special case it. So here we go. --- demo/demo.zig | 16 +++++++--- source/command.zig | 70 +++++++++++++++++++++++++++++-------------- source/meta.zig | 2 +- source/parameters.zig | 10 +++---- source/parser.zig | 16 +++++++++- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/demo/demo.zig b/demo/demo.zig index 61563ed..07f0aff 100644 --- a/demo/demo.zig +++ b/demo/demo.zig @@ -6,7 +6,11 @@ const CommandBuilder = noclip.CommandBuilder; const Choice = enum { first, second }; const cli = cmd: { - var cmd = CommandBuilder(u32).init(); + var cmd = CommandBuilder(u32).init( + \\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", @@ -51,7 +55,11 @@ const cli = cmd: { }; const subcommand = cmd: { - var cmd = CommandBuilder(void).init(); + var cmd = CommandBuilder(void).init( + \\Perform some sort of work + \\ + \\This subcommand is a mystery. It probably does something, but nobody is sure what. + ); cmd.add_flag(.{}, .{ .name = "flag", .truthy = .{ .short_tag = "-f", .long_tag = "--flag" }, @@ -78,10 +86,10 @@ pub fn main() !void { defer arena.deinit(); const allocator = arena.allocator(); - var parser = cli.bind(cli_handler, allocator); + var parser = cli.create_parser(cli_handler, allocator); var context: u32 = 2; - var subcon = subcommand.bind(sub_handler, allocator); + var subcon = subcommand.create_parser(sub_handler, allocator); try parser.add_subcommand("verb", subcon.interface()); const iface = parser.interface(&context); diff --git a/source/command.zig b/source/command.zig index 0f671eb..c95b754 100644 --- a/source/command.zig +++ b/source/command.zig @@ -9,6 +9,7 @@ const ValueCount = parameters.ValueCount; const ParameterGenerics = parameters.ParameterGenerics; const OptionConfig = parameters.OptionConfig; const FlagConfig = parameters.FlagConfig; +const ShortLongPair = parameters.ShortLongPair; const FlagBias = parameters.FlagBias; const make_option = parameters.make_option; const make_argument = parameters.make_argument; @@ -72,13 +73,56 @@ fn BuilderGenerics(comptime UserContext: type) type { } pub fn CommandBuilder(comptime UserContext: type) type { + const HelpFlagOption = OptionConfig(.{ + .UserContext = UserContext, + .OutputType = bool, + .param_type = .Nominal, + .value_count = .flag, + .multi = false, + }); + return struct { - param_spec: ncmeta.MutableTuple = .{}, + param_spec: ncmeta.TupleBuilder = .{}, + // this is a strange hack, but it's easily the path of least resistance + help_flag: ShortLongPair = .{ .short_tag = "-h", .long_tag = "--help" }, + description: []const u8, + /// if any subcommands are provided, one of them must be specified, or the command has failed. + subcommand_required: bool = true, pub const UserContextType = UserContext; - pub fn init() @This() { - return .{}; + pub fn init(comptime description: []const u8) @This() { + return .{ .description = description }; + } + + pub fn create_parser( + comptime self: @This(), + comptime callback: self.CallbackSignature(), + allocator: std.mem.Allocator, + ) Parser(self, callback) { + return Parser(self, callback){ + .allocator = allocator, + .subcommands = std.hash_map.StringHashMap(ParserInterface).init(allocator), + }; + } + + pub fn set_help_flag( + comptime self: *@This(), + comptime tags: ShortLongPair, + ) void { + self.help_flag = tags; + } + + pub fn mock_help_parameter(comptime self: @This()) ?HelpFlagOption { + if (self.help_flag.short_tag == null and self.help_flag.long_tag == null) return null; + + return HelpFlagOption{ + .name = "_internal_help_flag", + .short_tag = self.help_flag.short_tag, + .long_tag = self.help_flag.long_tag, + .description = "Print this help message and exit", + .flag_bias = true, + }; } pub fn add_argument( @@ -104,15 +148,6 @@ pub fn CommandBuilder(comptime UserContext: type) type { self.param_spec.add(make_option(bgen.opt_gen(), config)); } - pub fn set_help_flag( - comptime self: *@This(), - comptime bgen: BuilderGenerics(UserContext), - comptime config: FlagConfig(bgen.flag_gen()), - ) void { - _ = self; - _ = config; - } - pub fn add_flag( comptime self: *@This(), comptime bgen: BuilderGenerics(UserContext), @@ -372,16 +407,5 @@ pub fn CommandBuilder(comptime UserContext: type) type { } }); } } - - pub fn bind( - comptime self: @This(), - comptime callback: self.CallbackSignature(), - allocator: std.mem.Allocator, - ) Parser(self, callback) { - return Parser(self, callback){ - .allocator = allocator, - .subcommands = std.hash_map.StringHashMap(ParserInterface).init(allocator), - }; - } }; } diff --git a/source/meta.zig b/source/meta.zig index c7a8bc3..d3a8418 100644 --- a/source/meta.zig +++ b/source/meta.zig @@ -112,7 +112,7 @@ pub fn copy_struct(comptime T: type, source: T, field_overrides: anytype) T { /// Stores type-erased pointers to items in comptime extensible data structures, /// which allows e.g. assembling a tuple through multiple calls rather than all /// at once. -pub const MutableTuple = struct { +pub const TupleBuilder = struct { pointers: []const *const anyopaque = &[0]*const anyopaque{}, types: []const type = &[0]type{}, diff --git a/source/parameters.zig b/source/parameters.zig index 3048ac2..ac10334 100644 --- a/source/parameters.zig +++ b/source/parameters.zig @@ -155,12 +155,12 @@ pub fn OptionConfig(comptime generics: ParameterGenerics) type { }; } -pub fn FlagConfig(comptime generics: ParameterGenerics) type { - const ShortLongPair = struct { - short_tag: ?[]const u8 = null, - long_tag: ?[]const u8 = null, - }; +pub const ShortLongPair = struct { + short_tag: ?[]const u8 = null, + long_tag: ?[]const u8 = null, +}; +pub fn FlagConfig(comptime generics: ParameterGenerics) type { return struct { name: []const u8, diff --git a/source/parser.zig b/source/parser.zig index 4913bc2..1f550db 100644 --- a/source/parser.zig +++ b/source/parser.zig @@ -62,9 +62,9 @@ fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type { // be extremely type-sloppy here, which simplifies the signature. pub fn Parser(comptime command: anytype, comptime callback: anytype) type { const UserContext = @TypeOf(command).UserContextType; + const parameters = command.generate(); const Intermediate = command.Intermediate(); const Output = command.Output(); - const parameters = command.generate(); return struct { intermediate: Intermediate = .{}, @@ -237,6 +237,10 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type { arg: []const u8, argit: *ncmeta.SliceIterator([][:0]u8), ) ParseError!void { + if (comptime command.help_flag.long_tag) |long| + if (std.mem.eql(u8, arg, long)) + self.print_help(); + inline for (comptime parameters) |param| { const PType = @TypeOf(param); // removing the comptime here causes the compiler to die @@ -263,6 +267,10 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type { remaining: usize, argit: *ncmeta.SliceIterator([][:0]u8), ) ParseError!void { + if (comptime command.help_flag.short_tag) |short| + if (arg == short[1]) + self.print_help(); + inline for (comptime parameters) |param| { const PType = @TypeOf(param); // removing the comptime here causes the compiler to die @@ -414,5 +422,11 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type { } } } + + fn print_help(self: @This()) void { + _ = self; + std.debug.print("help!!!\n", .{}); + std.process.exit(0); + } }; }