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.
This commit is contained in:
torque 2023-04-01 13:04:02 -07:00
parent 7c9273605d
commit c3b31b2274
Signed by: torque
SSH Key Fingerprint: SHA256:nCrXefBNo6EbjNSQhv0nXmEg/VuNq3sMF5b8zETw3Tk
5 changed files with 80 additions and 34 deletions

View File

@ -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);

View File

@ -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),
};
}
};
}

View File

@ -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{},

View File

@ -155,12 +155,12 @@ pub fn OptionConfig(comptime generics: ParameterGenerics) type {
};
}
pub fn FlagConfig(comptime generics: ParameterGenerics) type {
const ShortLongPair = struct {
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,

View File

@ -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);
}
};
}