sorta help text generation
This mostly works. Subcommands are utterly broken because we blindly consume an additional argument to get the program name, which we should not do. This code was always kind of spaghetti, but it's getting worse. I want to refactor it into something that doesn't make me cringe, but at the same time, this project was intended to be a means to an end rather than the end itself, and it kind of feels a bit silly to spend a ton of time on it. On the other hand, relying on it for other projects seems silly if it's a fragile mess. The goal was to get it into a usable state and then hack on it as necessary, but it still has a ways to go to get there, and working on it is kind of painful, in an existential fashion. Perhaps I will attempt to rewrite it, get halfway, and stall forever. Thanks for reading my cool commit message blog. Bye.
This commit is contained in:
parent
9480d23f2d
commit
5eee6ecde0
@ -5,27 +5,47 @@ const context: []const u8 = "hello friend";
|
|||||||
const ContextType = @TypeOf(context);
|
const ContextType = @TypeOf(context);
|
||||||
|
|
||||||
const subcommand = blk: {
|
const subcommand = blk: {
|
||||||
var cmd = noclip.Command(ContextType, .{ .name = "subcommand", .help = "this a sub command" });
|
var cmd = noclip.Command(ContextType, .{ .name = "verb", .help = "this a sub command" });
|
||||||
cmd.add(cmd.defaultHelpFlag);
|
cmd.add(cmd.defaultHelpFlag);
|
||||||
cmd.add(cmd.StringOption{ .name = "meta", .short = "-m" });
|
cmd.add(cmd.StringOption{ .name = "meta", .short = "-m" });
|
||||||
cmd.add(cmd.StringArgument{ .name = "sub" });
|
cmd.add(cmd.StringArgument{ .name = "argument" });
|
||||||
|
cmd.add(cmd.Argument(u32){ .name = "another", .default = 0 });
|
||||||
break :blk cmd;
|
break :blk cmd;
|
||||||
};
|
};
|
||||||
|
|
||||||
const command = blk: {
|
const command = blk: {
|
||||||
var cmd = noclip.Command(ContextType, .{ .name = "main", .help = "main CLI entry point" });
|
var cmd = noclip.Command(ContextType, .{
|
||||||
|
.name = "main",
|
||||||
|
.help =
|
||||||
|
\\This is the main CLI entry point for the noclip demo
|
||||||
|
\\
|
||||||
|
\\This program demonstrates the major features of noclip both in terms of API
|
||||||
|
\\usage (in its source code) and argument parsing (in its execution).
|
||||||
|
,
|
||||||
|
});
|
||||||
|
cmd.add(cmd.defaultHelpFlag);
|
||||||
cmd.add(cmd.Flag{ .name = "flag", .truthy = .{ .short = "-f", .long = "--flag" }, .falsy = .{ .long = "--no-flag" } });
|
cmd.add(cmd.Flag{ .name = "flag", .truthy = .{ .short = "-f", .long = "--flag" }, .falsy = .{ .long = "--no-flag" } });
|
||||||
cmd.add(cmd.StringOption{
|
cmd.add(cmd.StringOption{
|
||||||
.name = "input",
|
.name = "input",
|
||||||
.short = "-i",
|
.short = "-i",
|
||||||
.long = "--input",
|
.long = "--input",
|
||||||
.handler = printHandler,
|
.help = "some generic input",
|
||||||
|
.default = "in",
|
||||||
.envVar = "OPTS_INPUT",
|
.envVar = "OPTS_INPUT",
|
||||||
});
|
});
|
||||||
cmd.add(cmd.StringOption{ .name = "output", .long = "--output", .default = "waoh" });
|
cmd.add(cmd.StringOption{
|
||||||
cmd.add(cmd.Option(i32){ .name = "number", .short = "-n", .long = "--number" });
|
.name = "output",
|
||||||
cmd.add(cmd.StringArgument{ .name = "argument" });
|
.long = "--output",
|
||||||
cmd.add(cmd.Argument(u32){ .name = "another", .default = 0 });
|
.default = "waoh",
|
||||||
|
.help = "name of the output",
|
||||||
|
});
|
||||||
|
cmd.add(cmd.Option(i32){
|
||||||
|
.name = "number",
|
||||||
|
.short = "-n",
|
||||||
|
.long = "--number",
|
||||||
|
.help = "a number",
|
||||||
|
.default = 0,
|
||||||
|
});
|
||||||
|
|
||||||
cmd.add(subcommand.Parser(subCallback));
|
cmd.add(subcommand.Parser(subCallback));
|
||||||
break :blk cmd;
|
break :blk cmd;
|
||||||
@ -40,34 +60,27 @@ pub fn subCallback(_: ContextType, result: subcommand.CommandResult()) !void {
|
|||||||
std.debug.print(
|
std.debug.print(
|
||||||
\\subcommand: {{
|
\\subcommand: {{
|
||||||
\\ .meta = {s}
|
\\ .meta = {s}
|
||||||
\\ .sub = {s}
|
\\ .argument = {s}
|
||||||
|
\\ .another = {d}
|
||||||
\\}}
|
\\}}
|
||||||
\\
|
\\
|
||||||
,
|
,
|
||||||
.{ result.meta, result.sub },
|
.{ result.meta, result.argument, result.another },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mainCommand(_: ContextType, result: command.CommandResult()) !void {
|
pub fn mainCommand(_: ContextType, result: command.CommandResult()) !void {
|
||||||
|
// std.debug.print("{any}", .{result});
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
\\arguments: {{
|
\\arguments: {{
|
||||||
\\ .flag = {any}
|
\\ .flag = {any}
|
||||||
\\ .input = {s}
|
\\ .input = {s}
|
||||||
\\ .output = {s}
|
\\ .output = {s}
|
||||||
\\ .number = {d}
|
\\ .number = {d}
|
||||||
\\ .argument = {s}
|
|
||||||
\\ .another = {d}
|
|
||||||
\\}}
|
\\}}
|
||||||
\\
|
\\
|
||||||
,
|
,
|
||||||
.{
|
.{ result.flag, result.input, result.output, result.number },
|
||||||
result.flag,
|
|
||||||
result.input,
|
|
||||||
result.output,
|
|
||||||
result.number,
|
|
||||||
result.argument,
|
|
||||||
result.another,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +92,6 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = arena.allocator();
|
const allocator = arena.allocator();
|
||||||
var argit = try std.process.argsWithAllocator(allocator);
|
var argit = try std.process.argsWithAllocator(allocator);
|
||||||
_ = argit.next();
|
|
||||||
|
|
||||||
try parser.execute(allocator, std.process.ArgIterator, &argit, context);
|
try parser.execute(allocator, std.process.ArgIterator, &argit, context);
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,20 @@ fn GenCommand(comptime UserContext: type, comptime cData: params.CommandData) ty
|
|||||||
return struct {
|
return struct {
|
||||||
argspec: meta.MutableTuple = .{},
|
argspec: meta.MutableTuple = .{},
|
||||||
|
|
||||||
StringOption: type = params.Option(.{ .Output = []const u8, .UserContext = UserContext }),
|
StringOption: type = params.StringOption(UserContext),
|
||||||
StringArgument: type = params.Argument(.{ .Output = []const u8, .UserContext = UserContext }),
|
StringArgument: type = params.StringArg(UserContext),
|
||||||
Flag: type = params.Flag(UserContext),
|
Flag: type = params.Flag(UserContext),
|
||||||
defaultHelpFlag: params.Flag(UserContext) = HelpFlag(.{}),
|
defaultHelpFlag: params.Flag(UserContext) = HelpFlag(undefined, .{}),
|
||||||
|
|
||||||
// have to provide the first argument in order for these functions to be
|
|
||||||
// accessible from an instance, which is kind of annoying.
|
|
||||||
pub fn Option(comptime _: @This(), comptime Output: type) type {
|
pub fn Option(comptime _: @This(), comptime Output: type) type {
|
||||||
return params.Option(.{ .Output = Output, .UserContext = UserContext });
|
return params.Option(.{ .Output = Output, .UserContext = UserContext });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Argument(comptime _: @This(), comptime Output: type) type {
|
pub fn Argument(comptime _: @This(), comptime Output: type) type {
|
||||||
return params.Argument(.{ .Output = Output, .UserContext = UserContext });
|
return params.Argument(.{ .Output = Output, .UserContext = UserContext });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn HelpFlag(comptime args: params.HelpFlagArgs) params.Flag(UserContext) {
|
pub fn HelpFlag(comptime _: @This(), comptime args: params.HelpFlagArgs) params.Flag(UserContext) {
|
||||||
return params.HelpFlag(UserContext, args);
|
return params.HelpFlag(UserContext, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,12 +34,22 @@ pub fn CommandParser(
|
|||||||
comptime UserContext: type,
|
comptime UserContext: type,
|
||||||
comptime callback: *const fn (UserContext, CommandResult(spec, UserContext)) anyerror!void,
|
comptime callback: *const fn (UserContext, CommandResult(spec, UserContext)) anyerror!void,
|
||||||
) type {
|
) type {
|
||||||
comptime var argCount = 0;
|
const param_count: struct {
|
||||||
comptime for (spec) |param| {
|
opts: comptime_int,
|
||||||
switch (@TypeOf(param).brand) {
|
args: comptime_int,
|
||||||
.Argument => argCount += 1,
|
subs: comptime_int,
|
||||||
.Option, .Flag, .Command => continue,
|
} = comptime comp: {
|
||||||
|
var optc = 0;
|
||||||
|
var argc = 0;
|
||||||
|
var subc = 0;
|
||||||
|
for (spec) |param| {
|
||||||
|
switch (@TypeOf(param).brand) {
|
||||||
|
.Argument => argc += 1,
|
||||||
|
.Option, .Flag => optc += 1,
|
||||||
|
.Command => subc += 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
break :comp .{ .opts = optc, .args = argc, .subs = subc };
|
||||||
};
|
};
|
||||||
|
|
||||||
const ResultType = CommandResult(spec, UserContext);
|
const ResultType = CommandResult(spec, UserContext);
|
||||||
@ -63,6 +73,15 @@ pub fn CommandParser(
|
|||||||
|
|
||||||
try extractEnvVars(alloc, &result, &required, context);
|
try extractEnvVars(alloc, &result, &required, context);
|
||||||
|
|
||||||
|
// TODO: this does not even slightly work with subcommands
|
||||||
|
const progName = std.fs.path.basename(argit.next() orelse unreachable);
|
||||||
|
|
||||||
|
// TODO: only do this if the help flag has been passed. Alternatively, try
|
||||||
|
// to assemble this at comptime?
|
||||||
|
var helpDescription: params.CommandData = .{ .name = data.name };
|
||||||
|
try buildHelpDescription(progName, &helpDescription, alloc);
|
||||||
|
defer alloc.free(helpDescription.help);
|
||||||
|
|
||||||
var seenArgs: u32 = 0;
|
var seenArgs: u32 = 0;
|
||||||
argloop: while (argit.next()) |arg| {
|
argloop: while (argit.next()) |arg| {
|
||||||
if (parseState == .Mixed and arg.len > 1 and arg[0] == '-') {
|
if (parseState == .Mixed and arg.len > 1 and arg[0] == '-') {
|
||||||
@ -88,8 +107,6 @@ pub fn CommandParser(
|
|||||||
specloop: inline for (spec) |param| {
|
specloop: inline for (spec) |param| {
|
||||||
switch (@TypeOf(param).brand) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Option => {
|
.Option => {
|
||||||
// have to force lower the handler to runtime
|
|
||||||
// var handler = param.handler.?;
|
|
||||||
if (param.long) |flag| {
|
if (param.long) |flag| {
|
||||||
if (std.mem.eql(u8, flag, arg)) {
|
if (std.mem.eql(u8, flag, arg)) {
|
||||||
if (comptime param.required()) {
|
if (comptime param.required()) {
|
||||||
@ -109,7 +126,7 @@ pub fn CommandParser(
|
|||||||
if (variant[0]) |flag| {
|
if (variant[0]) |flag| {
|
||||||
if (std.mem.eql(u8, flag, arg)) {
|
if (std.mem.eql(u8, flag, arg)) {
|
||||||
if (param.eager) |handler| {
|
if (param.eager) |handler| {
|
||||||
try handler(context, data);
|
try handler(context, helpDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.hideResult == false) {
|
if (param.hideResult == false) {
|
||||||
@ -132,7 +149,6 @@ pub fn CommandParser(
|
|||||||
specloop: inline for (spec) |param| {
|
specloop: inline for (spec) |param| {
|
||||||
switch (@TypeOf(param).brand) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Option => {
|
.Option => {
|
||||||
// var handler = param.handler.?;
|
|
||||||
if (param.short) |flag| {
|
if (param.short) |flag| {
|
||||||
if (flag[1] == shorty) {
|
if (flag[1] == shorty) {
|
||||||
if (comptime param.required()) {
|
if (comptime param.required()) {
|
||||||
@ -156,7 +172,7 @@ pub fn CommandParser(
|
|||||||
if (variant[0]) |flag| {
|
if (variant[0]) |flag| {
|
||||||
if (flag[1] == shorty) {
|
if (flag[1] == shorty) {
|
||||||
if (param.eager) |handler| {
|
if (param.eager) |handler| {
|
||||||
try handler(context, data);
|
try handler(context, helpDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.hideResult == false) {
|
if (param.hideResult == false) {
|
||||||
@ -193,7 +209,6 @@ pub fn CommandParser(
|
|||||||
if (comptime param.required()) {
|
if (comptime param.required()) {
|
||||||
@field(required, param.name) = true;
|
@field(required, param.name) = true;
|
||||||
}
|
}
|
||||||
// var handler = param.handler;
|
|
||||||
@field(result, param.name) = try param.handler.?(context, arg);
|
@field(result, param.name) = try param.handler.?(context, arg);
|
||||||
continue :argloop;
|
continue :argloop;
|
||||||
}
|
}
|
||||||
@ -208,14 +223,260 @@ pub fn CommandParser(
|
|||||||
try callback(context, result);
|
try callback(context, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn buildHelpDescription(
|
||||||
|
progName: []const u8,
|
||||||
|
inData: *params.CommandData,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
) !void {
|
||||||
|
var seen: u32 = 0;
|
||||||
|
var maxlen: usize = 0;
|
||||||
|
|
||||||
|
var argnames: [param_count.args][]const u8 = undefined;
|
||||||
|
var args: [param_count.args]ParamRow = undefined;
|
||||||
|
inline for (spec) |param| {
|
||||||
|
switch (@TypeOf(param).brand) {
|
||||||
|
.Argument => {
|
||||||
|
argnames[seen] = param.name;
|
||||||
|
args[seen] = try describeArgument(param, alloc);
|
||||||
|
maxlen = @max(args[seen].flags.len, maxlen);
|
||||||
|
seen += 1;
|
||||||
|
},
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seen = 0;
|
||||||
|
var rows: [param_count.opts]ParamRow = undefined;
|
||||||
|
inline for (spec) |param| {
|
||||||
|
const describer = switch (@TypeOf(param).brand) {
|
||||||
|
.Option => describeOption,
|
||||||
|
.Flag => describeFlag,
|
||||||
|
else => continue,
|
||||||
|
};
|
||||||
|
rows[seen] = try describer(param, alloc);
|
||||||
|
maxlen = @max(rows[seen].flags.len, maxlen);
|
||||||
|
seen += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
seen = 0;
|
||||||
|
var subs: [param_count.subs]ParamRow = undefined;
|
||||||
|
inline for (spec) |param| {
|
||||||
|
switch (@TypeOf(param).brand) {
|
||||||
|
.Command => {
|
||||||
|
subs[seen] = try describeSubcommand(param, alloc);
|
||||||
|
maxlen = @max(subs[seen].flags.len, maxlen);
|
||||||
|
seen += 1;
|
||||||
|
},
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
|
||||||
|
for (argnames) |name| {
|
||||||
|
try std.fmt.format(writer, " <{s}>", .{name});
|
||||||
|
}
|
||||||
|
|
||||||
|
const short_args = try buffer.toOwnedSlice();
|
||||||
|
defer alloc.free(short_args);
|
||||||
|
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
"Usage: {s}{s}{s}{s}\n\n",
|
||||||
|
.{
|
||||||
|
progName,
|
||||||
|
if (param_count.opts > 0) " [options]" else "",
|
||||||
|
if (param_count.args > 0) short_args else "",
|
||||||
|
if (param_count.subs > 0) " [subcommand] ..." else "",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try writer.writeAll(data.help);
|
||||||
|
|
||||||
|
if (param_count.args > 0) {
|
||||||
|
try writer.writeAll("\n\nArguments:\n");
|
||||||
|
|
||||||
|
for (args) |arg| {
|
||||||
|
defer arg.deinit(alloc);
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
" {[0]s: <[1]}{[2]s}\n",
|
||||||
|
.{ arg.flags, maxlen + 2, arg.description },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param_count.opts > 0) {
|
||||||
|
try writer.writeAll("\nOptions:\n");
|
||||||
|
|
||||||
|
for (rows) |row| {
|
||||||
|
defer row.deinit(alloc);
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
" {[0]s: <[1]}{[2]s}\n",
|
||||||
|
.{ row.flags, maxlen + 2, row.description },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param_count.subs > 0) {
|
||||||
|
try writer.writeAll("\nSubcommands:\n");
|
||||||
|
// try std.fmt.format(writer, "\nSubcommands {d}:\n", .{param_count.subs});
|
||||||
|
for (subs) |sub| {
|
||||||
|
defer sub.deinit(alloc);
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
" {[0]s: <[1]}{[2]s}\n",
|
||||||
|
.{ sub.flags, maxlen + 2, sub.description },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inData.help = try buffer.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParamRow = struct {
|
||||||
|
flags: []const u8,
|
||||||
|
description: []const u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: @This(), alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.flags);
|
||||||
|
alloc.free(self.description);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn describeArgument(comptime param: anytype, alloc: std.mem.Allocator) !ParamRow {
|
||||||
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
|
||||||
|
try writer.writeAll(param.name);
|
||||||
|
try std.fmt.format(writer, " ({s})", .{param.type_name()});
|
||||||
|
|
||||||
|
const flags = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
if (param.help) |help| {
|
||||||
|
try writer.writeAll(help);
|
||||||
|
}
|
||||||
|
if (param.required()) {
|
||||||
|
try writer.writeAll(" [required]");
|
||||||
|
}
|
||||||
|
const description = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
return ParamRow{ .flags = flags, .description = description };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describeOption(comptime param: anytype, alloc: std.mem.Allocator) !ParamRow {
|
||||||
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
|
||||||
|
if (param.envVar) |varName| {
|
||||||
|
try std.fmt.format(writer, "{s}", .{varName});
|
||||||
|
}
|
||||||
|
if (param.short) |short| {
|
||||||
|
if (buffer.items.len > 0) {
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
}
|
||||||
|
try writer.writeAll(short);
|
||||||
|
}
|
||||||
|
if (param.long) |long| {
|
||||||
|
if (buffer.items.len > 0) {
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
}
|
||||||
|
try writer.writeAll(long);
|
||||||
|
}
|
||||||
|
try std.fmt.format(writer, " ({s})", .{param.type_name()});
|
||||||
|
|
||||||
|
const flags = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
if (param.help) |help| {
|
||||||
|
try writer.writeAll(help);
|
||||||
|
}
|
||||||
|
if (param.required()) {
|
||||||
|
try writer.writeAll(" [required]");
|
||||||
|
}
|
||||||
|
const description = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
return ParamRow{ .flags = flags, .description = description };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describeFlag(comptime param: anytype, alloc: std.mem.Allocator) !ParamRow {
|
||||||
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
|
||||||
|
var truthy_seen: bool = false;
|
||||||
|
var falsy_seen: bool = false;
|
||||||
|
|
||||||
|
if (param.truthy.short) |short| {
|
||||||
|
try writer.writeAll(short);
|
||||||
|
truthy_seen = true;
|
||||||
|
}
|
||||||
|
if (param.truthy.long) |long| {
|
||||||
|
if (truthy_seen) {
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
}
|
||||||
|
try writer.writeAll(long);
|
||||||
|
truthy_seen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.falsy.short) |short| {
|
||||||
|
if (truthy_seen) {
|
||||||
|
try writer.writeAll("/");
|
||||||
|
}
|
||||||
|
try writer.writeAll(short);
|
||||||
|
falsy_seen = true;
|
||||||
|
}
|
||||||
|
if (param.falsy.long) |long| {
|
||||||
|
if (falsy_seen) {
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
} else if (truthy_seen) {
|
||||||
|
try writer.writeAll("/");
|
||||||
|
}
|
||||||
|
try writer.writeAll(long);
|
||||||
|
falsy_seen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.envVar) |varName| {
|
||||||
|
try std.fmt.format(writer, " ({s})", .{varName});
|
||||||
|
}
|
||||||
|
|
||||||
|
const flags = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
if (param.help) |help| {
|
||||||
|
try writer.writeAll(help);
|
||||||
|
}
|
||||||
|
if (param.required()) {
|
||||||
|
try writer.writeAll(" [required]");
|
||||||
|
}
|
||||||
|
const description = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
return ParamRow{ .flags = flags, .description = description };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describeSubcommand(comptime param: anytype, alloc: std.mem.Allocator) !ParamRow {
|
||||||
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
|
||||||
|
const paramdata = @TypeOf(param).data;
|
||||||
|
|
||||||
|
try writer.writeAll(paramdata.name);
|
||||||
|
|
||||||
|
const flags = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
try writer.writeAll(paramdata.help);
|
||||||
|
const description = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
return ParamRow{ .flags = flags, .description = description };
|
||||||
|
}
|
||||||
|
|
||||||
pub fn OutType() type {
|
pub fn OutType() type {
|
||||||
return CommandResult(spec, UserContext);
|
return CommandResult(spec, UserContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn checkErrors(seenArgs: u32, required: RequiredType) OptionError!void {
|
inline fn checkErrors(seenArgs: u32, required: RequiredType) OptionError!void {
|
||||||
if (seenArgs < argCount) {
|
if (seenArgs < param_count.args) {
|
||||||
return OptionError.MissingArgument;
|
return OptionError.MissingArgument;
|
||||||
} else if (seenArgs > argCount) {
|
} else if (seenArgs > param_count.args) {
|
||||||
return OptionError.ExtraArguments;
|
return OptionError.ExtraArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +552,7 @@ pub fn CommandParser(
|
|||||||
.Option => {
|
.Option => {
|
||||||
if (param.envVar) |want| {
|
if (param.envVar) |want| {
|
||||||
if (env.get(want)) |value| {
|
if (env.get(want)) |value| {
|
||||||
if (param.required()) {
|
if (comptime param.required()) {
|
||||||
@field(required, param.name) = true;
|
@field(required, param.name) = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ pub const ArgCount = union(enum) {
|
|||||||
pub const ParameterArgs = struct {
|
pub const ParameterArgs = struct {
|
||||||
Output: type,
|
Output: type,
|
||||||
UserContext: type,
|
UserContext: type,
|
||||||
|
nice_type: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn Option(comptime args: ParameterArgs) type {
|
pub fn Option(comptime args: ParameterArgs) type {
|
||||||
@ -67,13 +68,18 @@ pub fn Option(comptime args: ParameterArgs) type {
|
|||||||
pub fn required(self: @This()) bool {
|
pub fn required(self: @This()) bool {
|
||||||
return !@TypeOf(self).mayBeOptional and self.default == null;
|
return !@TypeOf(self).mayBeOptional and self.default == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_name(self: @This()) []const u8 {
|
||||||
|
if (args.nice_type) |name| return name;
|
||||||
|
return @typeName(@TypeOf(self).ResultType);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn StringOption(comptime UserContext: type) type {
|
pub fn StringOption(comptime UserContext: type) type {
|
||||||
return Option(.{ .Output = []const u8, .UserContext = UserContext });
|
return Option(.{ .Output = []const u8, .UserContext = UserContext, .nice_type = "string" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// this could be Option(bool) except it allows truthy/falsy flag variants
|
// this could be Option(bool) except it allows truthy/falsy flag variants
|
||||||
@ -105,8 +111,12 @@ pub fn Flag(comptime UserContext: type) type {
|
|||||||
eager: ?*const fn (UserContext, CommandData) anyerror!void = null,
|
eager: ?*const fn (UserContext, CommandData) anyerror!void = null,
|
||||||
|
|
||||||
pub fn required(self: @This()) bool {
|
pub fn required(self: @This()) bool {
|
||||||
if (self.default) return true;
|
if (self.default) |_| return false;
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_name(_: @This()) []const u8 {
|
||||||
|
return "bool";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -131,7 +141,7 @@ pub const HelpFlagArgs = struct {
|
|||||||
name: []const u8 = "help",
|
name: []const u8 = "help",
|
||||||
short: ?*const [2]u8 = "-h",
|
short: ?*const [2]u8 = "-h",
|
||||||
long: ?[]const u8 = "--help",
|
long: ?[]const u8 = "--help",
|
||||||
help: []const u8 = "print this help message",
|
help: []const u8 = "print this help message and exit",
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn HelpFlag(comptime UserContext: type, comptime args: HelpFlagArgs) Flag(UserContext) {
|
pub fn HelpFlag(comptime UserContext: type, comptime args: HelpFlagArgs) Flag(UserContext) {
|
||||||
@ -172,11 +182,16 @@ pub fn Argument(comptime args: ParameterArgs) type {
|
|||||||
pub fn required(self: @This()) bool {
|
pub fn required(self: @This()) bool {
|
||||||
return !@TypeOf(self).mayBeOptional and self.default == null;
|
return !@TypeOf(self).mayBeOptional and self.default == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_name(self: @This()) []const u8 {
|
||||||
|
if (args.nice_type) |name| return name;
|
||||||
|
return @typeName(@TypeOf(self).ResultType);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn StringArg(comptime UserContext: type) type {
|
pub fn StringArg(comptime UserContext: type) type {
|
||||||
return Argument(.{ .Output = []const u8, .UserContext = UserContext });
|
return Argument(.{ .Output = []const u8, .UserContext = UserContext, .nice_type = "string" });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CommandData = struct {
|
pub const CommandData = struct {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user