help: implement subcommand descriptions
I believe we've produced a superset of the functionality that was present before rewriting all of the code. There are still a lot of fiddly little details that need to be thought through in order to produce something that is righteously flexible, but I think this is in reasonable shape to start inventing real-world uses for it. Adding some tests, cleaning up a little bit of the allocation handling (make better use of the arena allocators—we are definitely sort of leaking memory at the moment), and writing documentation are still on the roadmap.
This commit is contained in:
parent
facda65271
commit
0d5dd9b36c
@ -9,7 +9,7 @@ const cli = cmd: {
|
||||
var cmd = CommandBuilder(u32).init(
|
||||
\\The definitive noclip demonstration utility
|
||||
\\
|
||||
\\This command demonstrates the functionality of the noclip library. cool!!
|
||||
\\This command demonstrates the functionality of the noclip library. cool!
|
||||
);
|
||||
cmd.add_option(.{ .OutputType = struct { u8, u8 } }, .{
|
||||
.name = "test",
|
||||
@ -41,7 +41,6 @@ const cli = cmd: {
|
||||
.name = "multi",
|
||||
.short_tag = "-m",
|
||||
.long_tag = "--multi",
|
||||
.env_var = "NOCLIP_MULTI",
|
||||
.description = "multiple specification test option",
|
||||
});
|
||||
cmd.add_flag(.{}, .{
|
||||
@ -54,11 +53,16 @@ const cli = cmd: {
|
||||
cmd.add_flag(.{ .multi = true }, .{
|
||||
.name = "multiflag",
|
||||
.truthy = .{ .short_tag = "-M" },
|
||||
.env_var = "NOCLIP_MULTIFLAG",
|
||||
.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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub const ConversionError = error {
|
||||
pub const ConversionError = error{
|
||||
ConversionFailed,
|
||||
};
|
||||
|
||||
|
271
source/help.zig
271
source/help.zig
@ -4,8 +4,6 @@ const ncmeta = @import("./meta.zig");
|
||||
const parser = @import("./parser.zig");
|
||||
const FixedCount = @import("./parameters.zig").FixedCount;
|
||||
|
||||
// error.OutOfMemory
|
||||
|
||||
const AlignablePair = struct {
|
||||
left: []const u8,
|
||||
right: []const u8,
|
||||
@ -31,84 +29,129 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
||||
pub fn build_message(
|
||||
self: *@This(),
|
||||
name: []const u8,
|
||||
subcommands: std.hash_map.StringHashMap(parser.ParserInterface),
|
||||
subcommands: parser.CommandMap,
|
||||
) ![]const u8 {
|
||||
try self.describe_command(name, subcommands);
|
||||
|
||||
return self.writebuffer.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn describe_command(
|
||||
self: *@This(),
|
||||
name: []const u8,
|
||||
subcommands: std.hash_map.StringHashMap(parser.ParserInterface),
|
||||
) !void {
|
||||
const writer = self.writebuffer.writer();
|
||||
try writer.print(
|
||||
"Usage: {s}{s}{s}{s}\n\n",
|
||||
.{
|
||||
name,
|
||||
try self.option_brief(),
|
||||
if (help_info.arguments.len > 0) " <arguments>" else "",
|
||||
if (subcommands.count() > 0) " [<subcommand> ...]" else "",
|
||||
self.option_brief(),
|
||||
try self.args_brief(),
|
||||
self.subcommands_brief(subcommands),
|
||||
},
|
||||
);
|
||||
|
||||
try writer.writeAll(std.mem.trim(u8, command.description, " \n"));
|
||||
try writer.writeAll("\n\n");
|
||||
|
||||
const arguments = try self.describe_arguments();
|
||||
const options = try self.describe_options();
|
||||
if (options.pairs.len > 0) {
|
||||
try writer.writeAll("Options:\n");
|
||||
}
|
||||
const env_vars = try self.describe_env();
|
||||
const subcs = try self.describe_subcommands(subcommands);
|
||||
const max_just = @max(arguments.just, @max(options.just, @max(env_vars.just, subcs.just)));
|
||||
|
||||
for (options.pairs) |pair| {
|
||||
try writer.print(
|
||||
" {[0]s: <[1]}{[2]s}\n",
|
||||
.{ pair.left, options.just + 3, pair.right },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn option_brief(self: @This()) ![]const u8 {
|
||||
if (comptime help_info.options.len > 1) {
|
||||
return " [options...]";
|
||||
} else if (comptime help_info.options.len == 1) {
|
||||
return " [option]";
|
||||
} else if (comptime help_info.options.len == 0) {
|
||||
return "";
|
||||
} else {
|
||||
var buffer = std.ArrayList(u8).init(self.writebuffer.allocator);
|
||||
const writer = buffer.writer();
|
||||
|
||||
for (comptime help_info.options) |opt| {
|
||||
try writer.writeAll(" [");
|
||||
|
||||
var written = false;
|
||||
if (opt.short_truthy) |tag| {
|
||||
try writer.writeAll(tag);
|
||||
written = true;
|
||||
}
|
||||
if (opt.long_truthy) |tag| {
|
||||
if (written) try writer.writeAll(" | ");
|
||||
try writer.writeAll(tag);
|
||||
written = true;
|
||||
}
|
||||
if (opt.short_falsy) |tag| {
|
||||
if (written) try writer.writeAll(" | ");
|
||||
try writer.writeAll(tag);
|
||||
written = true;
|
||||
}
|
||||
if (opt.long_falsy) |tag| {
|
||||
if (written) try writer.writeAll(" | ");
|
||||
try writer.writeAll(tag);
|
||||
}
|
||||
|
||||
try writer.writeAll("]");
|
||||
if (arguments.pairs.len > 0) {
|
||||
try writer.writeAll("Arguments:\n");
|
||||
for (arguments.pairs) |pair| {
|
||||
try writer.print(
|
||||
" {[0]s: <[1]}{[2]s}\n",
|
||||
.{ pair.left, max_just + 3, pair.right },
|
||||
);
|
||||
}
|
||||
|
||||
return buffer.toOwnedSlice();
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
if (options.pairs.len > 0) {
|
||||
try writer.writeAll("Options:\n");
|
||||
for (options.pairs) |pair| {
|
||||
try writer.print(
|
||||
" {[0]s: <[1]}{[2]s}\n",
|
||||
.{ pair.left, max_just + 3, pair.right },
|
||||
);
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
if (env_vars.pairs.len > 0) {
|
||||
try writer.writeAll("Environment variables:\n");
|
||||
for (env_vars.pairs) |pair| {
|
||||
try writer.print(
|
||||
" {[0]s: <[1]}{[2]s}\n",
|
||||
.{ pair.left, max_just + 3, pair.right },
|
||||
);
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
if (subcs.pairs.len > 0) {
|
||||
try writer.writeAll("Subcommands:\n");
|
||||
for (subcs.pairs) |pair| {
|
||||
try writer.print(
|
||||
" {[0]s: <[1]}{[2]s}\n",
|
||||
.{ pair.left, max_just + 3, pair.right },
|
||||
);
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
return self.writebuffer.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn option_brief(_: @This()) []const u8 {
|
||||
return if (comptime help_info.options.len > 0)
|
||||
" [options...]"
|
||||
else
|
||||
"";
|
||||
}
|
||||
|
||||
fn args_brief(self: @This()) ![]const u8 {
|
||||
var buf = std.ArrayList(u8).init(self.writebuffer.allocator);
|
||||
const writer = buf.writer();
|
||||
|
||||
for (comptime help_info.arguments) |arg| {
|
||||
try writer.writeAll(" ");
|
||||
if (!arg.required) try writer.writeAll("[");
|
||||
try writer.print("<{s}>", .{arg.name});
|
||||
if (!arg.required) try writer.writeAll("]");
|
||||
}
|
||||
|
||||
return buf.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn subcommands_brief(
|
||||
_: @This(),
|
||||
subcommands: parser.CommandMap,
|
||||
) []const u8 {
|
||||
return if (subcommands.count() > 0)
|
||||
" <subcommand ...>"
|
||||
else
|
||||
"";
|
||||
}
|
||||
|
||||
fn describe_arguments(self: @This()) !OptionDescription {
|
||||
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
|
||||
|
||||
var just: usize = 0;
|
||||
for (comptime help_info.arguments) |arg| {
|
||||
if (arg.description.len == 0) continue;
|
||||
|
||||
const pair: AlignablePair = .{
|
||||
.left = arg.name,
|
||||
.right = arg.description,
|
||||
};
|
||||
if (pair.left.len > just) just = pair.left.len;
|
||||
try pairs.append(pair);
|
||||
}
|
||||
|
||||
return .{
|
||||
.pairs = try pairs.toOwnedSlice(),
|
||||
.just = just,
|
||||
};
|
||||
}
|
||||
|
||||
fn describe_options(self: @This()) !OptionDescription {
|
||||
@ -163,23 +206,73 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
||||
|
||||
const left = try buffer.toOwnedSlice();
|
||||
|
||||
if (comptime opt.required) {
|
||||
try writer.writeAll("[required]");
|
||||
}
|
||||
|
||||
if (comptime opt.description.len > 0) {
|
||||
if (buffer.items.len > 0) try writer.writeAll(" ");
|
||||
try writer.writeAll(opt.description);
|
||||
}
|
||||
|
||||
if (comptime opt.env_var) |env| {
|
||||
if (buffer.items.len > 0) try writer.writeAll(" ");
|
||||
try writer.print("(env: {s})", .{env});
|
||||
}
|
||||
|
||||
if (comptime opt.default) |def| {
|
||||
if (buffer.items.len > 0) try writer.writeAll(" ");
|
||||
try writer.print("(default: {s})", .{def});
|
||||
}
|
||||
|
||||
if (comptime opt.required) {
|
||||
if (buffer.items.len > 0) try writer.writeAll(" ");
|
||||
try writer.writeAll("[required]");
|
||||
}
|
||||
const right = try buffer.toOwnedSlice();
|
||||
|
||||
return .{ .left = left, .right = right };
|
||||
}
|
||||
|
||||
fn describe_env(self: @This()) !OptionDescription {
|
||||
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
|
||||
|
||||
var just: usize = 0;
|
||||
for (comptime help_info.env_vars) |env| {
|
||||
if (env.description.len == 0) continue;
|
||||
|
||||
const pair: AlignablePair = .{
|
||||
.left = env.env_var,
|
||||
.right = env.description,
|
||||
};
|
||||
if (pair.left.len > just) just = pair.left.len;
|
||||
try pairs.append(pair);
|
||||
}
|
||||
|
||||
return .{
|
||||
.pairs = try pairs.toOwnedSlice(),
|
||||
.just = just,
|
||||
};
|
||||
}
|
||||
|
||||
fn describe_subcommands(self: @This(), subcommands: parser.CommandMap) !OptionDescription {
|
||||
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
|
||||
|
||||
var just: usize = 0;
|
||||
var iter = subcommands.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
const subif = subcommands.get(key.*).?;
|
||||
const short = ncmeta.partition(u8, subif.describe(), "\n");
|
||||
|
||||
const pair: AlignablePair = .{
|
||||
.left = key.*,
|
||||
.right = short[0],
|
||||
};
|
||||
if (pair.left.len > just) just = pair.left.len;
|
||||
try pairs.append(pair);
|
||||
}
|
||||
|
||||
return .{
|
||||
.pairs = try pairs.toOwnedSlice(),
|
||||
.just = just,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -197,6 +290,7 @@ const OptHelp = struct {
|
||||
env_var: ?[]const u8 = null,
|
||||
description: []const u8 = "",
|
||||
type_name: []const u8 = "",
|
||||
extra: []const u8 = "",
|
||||
default: ?[]const u8 = null,
|
||||
// this is the pivot
|
||||
value_count: FixedCount = 0,
|
||||
@ -212,6 +306,7 @@ const EnvHelp = struct {
|
||||
|
||||
const ArgHelp = struct {
|
||||
name: []const u8 = "",
|
||||
description: []const u8 = "",
|
||||
type_name: []const u8 = "",
|
||||
multi: bool = false,
|
||||
required: bool = true,
|
||||
@ -228,22 +323,28 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
||||
|
||||
paramloop: for (command) |param| {
|
||||
const PType = @TypeOf(param);
|
||||
if (PType.param_type != .Nominal) continue :paramloop;
|
||||
if (PType.param_type == .Ordinal) {
|
||||
arguments = arguments ++ &[_]ArgHelp{.{
|
||||
.name = param.name,
|
||||
.description = param.description,
|
||||
.type_name = param.nice_type_name,
|
||||
.multi = PType.multi,
|
||||
.required = param.required,
|
||||
}};
|
||||
|
||||
continue :paramloop;
|
||||
}
|
||||
|
||||
if (!std.mem.eql(u8, last_name, param.name)) {
|
||||
if (last_name.len > 0) {
|
||||
if (last_option.short_truthy != null or
|
||||
last_option.long_truthy != null or
|
||||
last_option.short_falsy != null or
|
||||
last_option.long_falsy != null)
|
||||
{
|
||||
options = options ++ &[_]OptHelp{last_option};
|
||||
} else {
|
||||
if (env_only(last_option)) {
|
||||
env_vars = env_vars ++ &[_]EnvHelp{.{
|
||||
.env_var = last_option.env_var,
|
||||
.description = last_option.description,
|
||||
.default = last_option.default,
|
||||
}};
|
||||
} else {
|
||||
options = options ++ &[_]OptHelp{last_option};
|
||||
}
|
||||
}
|
||||
last_name = param.name;
|
||||
@ -272,6 +373,7 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
||||
last_option.description = param.description;
|
||||
last_option.required = param.required;
|
||||
last_option.multi = PType.multi;
|
||||
|
||||
if (param.default) |def| {
|
||||
var buf = ncmeta.ComptimeSliceBuffer{};
|
||||
const writer = buf.writer();
|
||||
@ -284,18 +386,14 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
||||
}
|
||||
|
||||
if (last_name.len > 0) {
|
||||
if (last_option.short_truthy != null or
|
||||
last_option.long_truthy != null or
|
||||
last_option.short_falsy != null or
|
||||
last_option.long_falsy != null)
|
||||
{
|
||||
options = options ++ &[_]OptHelp{last_option};
|
||||
} else {
|
||||
if (env_only(last_option)) {
|
||||
env_vars = env_vars ++ &[_]EnvHelp{.{
|
||||
.env_var = last_option.env_var,
|
||||
.env_var = last_option.env_var.?,
|
||||
.description = last_option.description,
|
||||
.default = last_option.default,
|
||||
}};
|
||||
} else {
|
||||
options = options ++ &[_]OptHelp{last_option};
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,3 +404,10 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
inline fn env_only(option: OptHelp) bool {
|
||||
return option.short_truthy == null and
|
||||
option.long_truthy == null and
|
||||
option.short_falsy == null and
|
||||
option.long_falsy == null;
|
||||
}
|
||||
|
@ -52,6 +52,26 @@ pub fn enum_length(comptime T: type) comptime_int {
|
||||
return @typeInfo(T).Enum.fields.len;
|
||||
}
|
||||
|
||||
pub fn partition(comptime T: type, input: []const T, wedge: []const T) [3][]const T {
|
||||
for (input, 0..) |candidate, idx| {
|
||||
for (wedge) |splitter| {
|
||||
if (candidate == splitter) {
|
||||
return [3][]const T{
|
||||
input[0..idx],
|
||||
input[idx..(idx + 1)],
|
||||
input[(idx + 1)..],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [3][]const T{
|
||||
input[0..],
|
||||
input[input.len..],
|
||||
input[input.len..],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ComptimeWriter(
|
||||
comptime Context: type,
|
||||
comptime writeFn: fn (comptime context: Context, comptime bytes: []const u8) error{}!usize,
|
||||
|
@ -3,11 +3,7 @@ const std = @import("std");
|
||||
const converters = @import("./converters.zig");
|
||||
const ConverterSignature = converters.ConverterSignature;
|
||||
|
||||
const ParameterType = enum {
|
||||
Nominal,
|
||||
Ordinal,
|
||||
Executable,
|
||||
};
|
||||
const ParameterType = enum { Nominal, Ordinal };
|
||||
|
||||
pub const FixedCount = u32;
|
||||
|
||||
|
@ -10,8 +10,9 @@ const NoclipError = errors.NoclipError;
|
||||
pub const ParserInterface = struct {
|
||||
const Vtable = struct {
|
||||
execute: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
|
||||
parse: *const fn (parser: *anyopaque, context: *anyopaque, args: [][:0]u8, env: std.process.EnvMap) anyerror!void,
|
||||
parse: *const fn (parser: *anyopaque, context: *anyopaque, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void,
|
||||
finish: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
|
||||
describe: *const fn () []const u8,
|
||||
};
|
||||
|
||||
parser: *anyopaque,
|
||||
@ -22,13 +23,17 @@ pub const ParserInterface = struct {
|
||||
return try self.methods.execute(self.parser, self.context);
|
||||
}
|
||||
|
||||
pub fn parse(self: @This(), args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
return try self.methods.parse(self.parser, self.context, args, env);
|
||||
pub fn parse(self: @This(), name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
return try self.methods.parse(self.parser, self.context, name, args, env);
|
||||
}
|
||||
|
||||
pub fn finish(self: @This()) anyerror!void {
|
||||
return try self.methods.finish(self.parser, self.context);
|
||||
}
|
||||
|
||||
pub fn describe(self: @This()) []const u8 {
|
||||
return self.methods.describe();
|
||||
}
|
||||
};
|
||||
|
||||
fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
|
||||
@ -41,6 +46,7 @@ fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
|
||||
.execute = ParserType.wrap_execute,
|
||||
.parse = ParserType.wrap_parse,
|
||||
.finish = ParserType.wrap_finish,
|
||||
.describe = ParserType.describe,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -53,12 +59,15 @@ fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
|
||||
.execute = ParserType.wrap_execute,
|
||||
.parse = ParserType.wrap_parse,
|
||||
.finish = ParserType.wrap_finish,
|
||||
.describe = ParserType.describe,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const CommandMap = std.hash_map.StringHashMap(ParserInterface);
|
||||
|
||||
// the parser is generated by the bind method of the CommandBuilder, so we can
|
||||
// be extremely type-sloppy here, which simplifies the signature.
|
||||
pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
@ -74,7 +83,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
progname: ?[]const u8 = null,
|
||||
has_global_tags: bool = false,
|
||||
allocator: std.mem.Allocator,
|
||||
subcommands: std.hash_map.StringHashMap(ParserInterface),
|
||||
subcommands: CommandMap,
|
||||
subcommand: ?ParserInterface = null,
|
||||
help_builder: help.HelpBuilder(command),
|
||||
|
||||
@ -98,13 +107,13 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
return try self.execute(context);
|
||||
}
|
||||
|
||||
fn wrap_parse(parser: *anyopaque, ctx: *anyopaque, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
fn wrap_parse(parser: *anyopaque, ctx: *anyopaque, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), parser));
|
||||
const context = if (@alignOf(UserContext) > 0)
|
||||
@ptrCast(*UserContext, @alignCast(@alignOf(UserContext), ctx))
|
||||
else
|
||||
@ptrCast(*UserContext, ctx);
|
||||
return try self.subparse(context, args, env);
|
||||
return try self.subparse(context, name, args, env);
|
||||
}
|
||||
|
||||
fn wrap_finish(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
|
||||
@ -116,12 +125,23 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
return try self.finish(context);
|
||||
}
|
||||
|
||||
pub fn subparse(self: *@This(), context: *UserContext, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
const sliceto = try self.parse(args);
|
||||
fn describe() []const u8 {
|
||||
return command.description;
|
||||
}
|
||||
|
||||
pub fn subparse(self: *@This(), context: *UserContext, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
const sliceto = try self.parse(name, args);
|
||||
try self.read_environment(env);
|
||||
try self.convert_eager(context);
|
||||
|
||||
if (self.subcommand) |verb| try verb.parse(args[sliceto..], env);
|
||||
if (self.subcommand) |verb| {
|
||||
const verbname = try std.mem.join(
|
||||
self.allocator,
|
||||
" ",
|
||||
&[_][]const u8{ name, args[sliceto - 1] },
|
||||
);
|
||||
try verb.parse(verbname, args[sliceto..], env);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self: *@This(), context: *UserContext) anyerror!void {
|
||||
@ -138,9 +158,9 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
if (args.len < 1) return ParseError.EmptyArgs;
|
||||
|
||||
self.progname = args[0];
|
||||
self.progname = std.fs.path.basename(args[0]);
|
||||
|
||||
try self.subparse(context, args[1..], env);
|
||||
try self.subparse(context, self.progname.?, args[1..], env);
|
||||
try self.finish(context);
|
||||
}
|
||||
|
||||
@ -158,6 +178,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
pub fn parse(
|
||||
self: *@This(),
|
||||
name: []const u8,
|
||||
args: [][:0]u8,
|
||||
) anyerror!usize {
|
||||
// run pre-parse pass if we have any global parameters
|
||||
@ -201,11 +222,11 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
if (!forced_ordinal and arg.len > 1 and arg[0] == '-') {
|
||||
if (arg.len > 2 and arg[1] == '-') {
|
||||
try self.parse_long_tag(arg, &argit);
|
||||
try self.parse_long_tag(name, arg, &argit);
|
||||
continue :argloop;
|
||||
} else if (arg.len > 1) {
|
||||
for (arg[1..], 1..) |short, idx| {
|
||||
try self.parse_short_tag(short, arg.len - idx - 1, &argit);
|
||||
try self.parse_short_tag(name, short, arg.len - idx - 1, &argit);
|
||||
}
|
||||
continue :argloop;
|
||||
}
|
||||
@ -227,12 +248,13 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
inline fn parse_long_tag(
|
||||
self: *@This(),
|
||||
name: []const u8,
|
||||
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();
|
||||
self.print_help(name);
|
||||
|
||||
inline for (comptime parameters) |param| {
|
||||
const PType = @TypeOf(param);
|
||||
@ -256,13 +278,14 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
inline fn parse_short_tag(
|
||||
self: *@This(),
|
||||
name: []const u8,
|
||||
arg: u8,
|
||||
remaining: usize,
|
||||
argit: *ncmeta.SliceIterator([][:0]u8),
|
||||
) ParseError!void {
|
||||
if (comptime command.help_flag.short_tag) |short|
|
||||
if (arg == short[1])
|
||||
self.print_help();
|
||||
self.print_help(name);
|
||||
|
||||
inline for (comptime parameters) |param| {
|
||||
const PType = @TypeOf(param);
|
||||
@ -416,10 +439,11 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help(self: *@This()) noreturn {
|
||||
fn print_help(self: *@This(), name: []const u8) noreturn {
|
||||
defer std.process.exit(0);
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
if (self.help_builder.build_message("test", self.subcommands)) |message|
|
||||
if (self.help_builder.build_message(name, self.subcommands)) |message|
|
||||
stderr.writeAll(message) catch return
|
||||
else |_|
|
||||
stderr.writeAll("There was a problem generating the help.") catch return;
|
||||
|
Loading…
x
Reference in New Issue
Block a user