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(
|
var cmd = CommandBuilder(u32).init(
|
||||||
\\The definitive noclip demonstration utility
|
\\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 } }, .{
|
cmd.add_option(.{ .OutputType = struct { u8, u8 } }, .{
|
||||||
.name = "test",
|
.name = "test",
|
||||||
@ -41,7 +41,6 @@ const cli = cmd: {
|
|||||||
.name = "multi",
|
.name = "multi",
|
||||||
.short_tag = "-m",
|
.short_tag = "-m",
|
||||||
.long_tag = "--multi",
|
.long_tag = "--multi",
|
||||||
.env_var = "NOCLIP_MULTI",
|
|
||||||
.description = "multiple specification test option",
|
.description = "multiple specification test option",
|
||||||
});
|
});
|
||||||
cmd.add_flag(.{}, .{
|
cmd.add_flag(.{}, .{
|
||||||
@ -54,11 +53,16 @@ const cli = cmd: {
|
|||||||
cmd.add_flag(.{ .multi = true }, .{
|
cmd.add_flag(.{ .multi = true }, .{
|
||||||
.name = "multiflag",
|
.name = "multiflag",
|
||||||
.truthy = .{ .short_tag = "-M" },
|
.truthy = .{ .short_tag = "-M" },
|
||||||
.env_var = "NOCLIP_MULTIFLAG",
|
|
||||||
.description = "multiple specification test flag ",
|
.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 }, .{
|
cmd.add_argument(.{ .OutputType = []const u8 }, .{
|
||||||
.name = "arg",
|
.name = "arg",
|
||||||
|
.description = "This is an argument that doesn't really do anything, but it's very important.",
|
||||||
});
|
});
|
||||||
|
|
||||||
break :cmd cmd;
|
break :cmd cmd;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
pub const ConversionError = error {
|
pub const ConversionError = error{
|
||||||
ConversionFailed,
|
ConversionFailed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
251
source/help.zig
251
source/help.zig
@ -4,8 +4,6 @@ const ncmeta = @import("./meta.zig");
|
|||||||
const parser = @import("./parser.zig");
|
const parser = @import("./parser.zig");
|
||||||
const FixedCount = @import("./parameters.zig").FixedCount;
|
const FixedCount = @import("./parameters.zig").FixedCount;
|
||||||
|
|
||||||
// error.OutOfMemory
|
|
||||||
|
|
||||||
const AlignablePair = struct {
|
const AlignablePair = struct {
|
||||||
left: []const u8,
|
left: []const u8,
|
||||||
right: []const u8,
|
right: []const u8,
|
||||||
@ -31,84 +29,129 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
|||||||
pub fn build_message(
|
pub fn build_message(
|
||||||
self: *@This(),
|
self: *@This(),
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
subcommands: std.hash_map.StringHashMap(parser.ParserInterface),
|
subcommands: parser.CommandMap,
|
||||||
) ![]const u8 {
|
) ![]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();
|
const writer = self.writebuffer.writer();
|
||||||
try writer.print(
|
try writer.print(
|
||||||
"Usage: {s}{s}{s}{s}\n\n",
|
"Usage: {s}{s}{s}{s}\n\n",
|
||||||
.{
|
.{
|
||||||
name,
|
name,
|
||||||
try self.option_brief(),
|
self.option_brief(),
|
||||||
if (help_info.arguments.len > 0) " <arguments>" else "",
|
try self.args_brief(),
|
||||||
if (subcommands.count() > 0) " [<subcommand> ...]" else "",
|
self.subcommands_brief(subcommands),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
try writer.writeAll(std.mem.trim(u8, command.description, " \n"));
|
try writer.writeAll(std.mem.trim(u8, command.description, " \n"));
|
||||||
try writer.writeAll("\n\n");
|
try writer.writeAll("\n\n");
|
||||||
|
|
||||||
|
const arguments = try self.describe_arguments();
|
||||||
const options = try self.describe_options();
|
const options = try self.describe_options();
|
||||||
if (options.pairs.len > 0) {
|
const env_vars = try self.describe_env();
|
||||||
try writer.writeAll("Options:\n");
|
const subcs = try self.describe_subcommands(subcommands);
|
||||||
|
const max_just = @max(arguments.just, @max(options.just, @max(env_vars.just, subcs.just)));
|
||||||
|
|
||||||
|
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 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.pairs.len > 0) {
|
||||||
|
try writer.writeAll("Options:\n");
|
||||||
for (options.pairs) |pair| {
|
for (options.pairs) |pair| {
|
||||||
try writer.print(
|
try writer.print(
|
||||||
" {[0]s: <[1]}{[2]s}\n",
|
" {[0]s: <[1]}{[2]s}\n",
|
||||||
.{ pair.left, options.just + 3, pair.right },
|
.{ pair.left, max_just + 3, pair.right },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn option_brief(self: @This()) ![]const u8 {
|
if (env_vars.pairs.len > 0) {
|
||||||
if (comptime help_info.options.len > 1) {
|
try writer.writeAll("Environment variables:\n");
|
||||||
return " [options...]";
|
for (env_vars.pairs) |pair| {
|
||||||
} else if (comptime help_info.options.len == 1) {
|
try writer.print(
|
||||||
return " [option]";
|
" {[0]s: <[1]}{[2]s}\n",
|
||||||
} else if (comptime help_info.options.len == 0) {
|
.{ pair.left, max_just + 3, pair.right },
|
||||||
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("]");
|
try writer.writeAll("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.toOwnedSlice();
|
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 {
|
fn describe_options(self: @This()) !OptionDescription {
|
||||||
@ -163,23 +206,73 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
|||||||
|
|
||||||
const left = try buffer.toOwnedSlice();
|
const left = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
|
if (comptime opt.required) {
|
||||||
|
try writer.writeAll("[required]");
|
||||||
|
}
|
||||||
|
|
||||||
if (comptime opt.description.len > 0) {
|
if (comptime opt.description.len > 0) {
|
||||||
|
if (buffer.items.len > 0) try writer.writeAll(" ");
|
||||||
try writer.writeAll(opt.description);
|
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 (comptime opt.default) |def| {
|
||||||
if (buffer.items.len > 0) try writer.writeAll(" ");
|
if (buffer.items.len > 0) try writer.writeAll(" ");
|
||||||
try writer.print("(default: {s})", .{def});
|
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();
|
const right = try buffer.toOwnedSlice();
|
||||||
|
|
||||||
return .{ .left = left, .right = right };
|
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,
|
env_var: ?[]const u8 = null,
|
||||||
description: []const u8 = "",
|
description: []const u8 = "",
|
||||||
type_name: []const u8 = "",
|
type_name: []const u8 = "",
|
||||||
|
extra: []const u8 = "",
|
||||||
default: ?[]const u8 = null,
|
default: ?[]const u8 = null,
|
||||||
// this is the pivot
|
// this is the pivot
|
||||||
value_count: FixedCount = 0,
|
value_count: FixedCount = 0,
|
||||||
@ -212,6 +306,7 @@ const EnvHelp = struct {
|
|||||||
|
|
||||||
const ArgHelp = struct {
|
const ArgHelp = struct {
|
||||||
name: []const u8 = "",
|
name: []const u8 = "",
|
||||||
|
description: []const u8 = "",
|
||||||
type_name: []const u8 = "",
|
type_name: []const u8 = "",
|
||||||
multi: bool = false,
|
multi: bool = false,
|
||||||
required: bool = true,
|
required: bool = true,
|
||||||
@ -228,22 +323,28 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
|||||||
|
|
||||||
paramloop: for (command) |param| {
|
paramloop: for (command) |param| {
|
||||||
const PType = @TypeOf(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 (!std.mem.eql(u8, last_name, param.name)) {
|
||||||
if (last_name.len > 0) {
|
if (last_name.len > 0) {
|
||||||
if (last_option.short_truthy != null or
|
if (env_only(last_option)) {
|
||||||
last_option.long_truthy != null or
|
|
||||||
last_option.short_falsy != null or
|
|
||||||
last_option.long_falsy != null)
|
|
||||||
{
|
|
||||||
options = options ++ &[_]OptHelp{last_option};
|
|
||||||
} else {
|
|
||||||
env_vars = env_vars ++ &[_]EnvHelp{.{
|
env_vars = env_vars ++ &[_]EnvHelp{.{
|
||||||
.env_var = last_option.env_var,
|
.env_var = last_option.env_var,
|
||||||
.description = last_option.description,
|
.description = last_option.description,
|
||||||
.default = last_option.default,
|
.default = last_option.default,
|
||||||
}};
|
}};
|
||||||
|
} else {
|
||||||
|
options = options ++ &[_]OptHelp{last_option};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_name = param.name;
|
last_name = param.name;
|
||||||
@ -272,6 +373,7 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
|||||||
last_option.description = param.description;
|
last_option.description = param.description;
|
||||||
last_option.required = param.required;
|
last_option.required = param.required;
|
||||||
last_option.multi = PType.multi;
|
last_option.multi = PType.multi;
|
||||||
|
|
||||||
if (param.default) |def| {
|
if (param.default) |def| {
|
||||||
var buf = ncmeta.ComptimeSliceBuffer{};
|
var buf = ncmeta.ComptimeSliceBuffer{};
|
||||||
const writer = buf.writer();
|
const writer = buf.writer();
|
||||||
@ -284,18 +386,14 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (last_name.len > 0) {
|
if (last_name.len > 0) {
|
||||||
if (last_option.short_truthy != null or
|
if (env_only(last_option)) {
|
||||||
last_option.long_truthy != null or
|
|
||||||
last_option.short_falsy != null or
|
|
||||||
last_option.long_falsy != null)
|
|
||||||
{
|
|
||||||
options = options ++ &[_]OptHelp{last_option};
|
|
||||||
} else {
|
|
||||||
env_vars = env_vars ++ &[_]EnvHelp{.{
|
env_vars = env_vars ++ &[_]EnvHelp{.{
|
||||||
.env_var = last_option.env_var,
|
.env_var = last_option.env_var.?,
|
||||||
.description = last_option.description,
|
.description = last_option.description,
|
||||||
.default = last_option.default,
|
.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;
|
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(
|
pub fn ComptimeWriter(
|
||||||
comptime Context: type,
|
comptime Context: type,
|
||||||
comptime writeFn: fn (comptime context: Context, comptime bytes: []const u8) error{}!usize,
|
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 converters = @import("./converters.zig");
|
||||||
const ConverterSignature = converters.ConverterSignature;
|
const ConverterSignature = converters.ConverterSignature;
|
||||||
|
|
||||||
const ParameterType = enum {
|
const ParameterType = enum { Nominal, Ordinal };
|
||||||
Nominal,
|
|
||||||
Ordinal,
|
|
||||||
Executable,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const FixedCount = u32;
|
pub const FixedCount = u32;
|
||||||
|
|
||||||
|
@ -10,8 +10,9 @@ const NoclipError = errors.NoclipError;
|
|||||||
pub const ParserInterface = struct {
|
pub const ParserInterface = struct {
|
||||||
const Vtable = struct {
|
const Vtable = struct {
|
||||||
execute: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
|
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,
|
finish: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
|
||||||
|
describe: *const fn () []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
parser: *anyopaque,
|
parser: *anyopaque,
|
||||||
@ -22,13 +23,17 @@ pub const ParserInterface = struct {
|
|||||||
return try self.methods.execute(self.parser, self.context);
|
return try self.methods.execute(self.parser, self.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(self: @This(), args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
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, args, env);
|
return try self.methods.parse(self.parser, self.context, name, args, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self: @This()) anyerror!void {
|
pub fn finish(self: @This()) anyerror!void {
|
||||||
return try self.methods.finish(self.parser, self.context);
|
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 {
|
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,
|
.execute = ParserType.wrap_execute,
|
||||||
.parse = ParserType.wrap_parse,
|
.parse = ParserType.wrap_parse,
|
||||||
.finish = ParserType.wrap_finish,
|
.finish = ParserType.wrap_finish,
|
||||||
|
.describe = ParserType.describe,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -53,12 +59,15 @@ fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
|
|||||||
.execute = ParserType.wrap_execute,
|
.execute = ParserType.wrap_execute,
|
||||||
.parse = ParserType.wrap_parse,
|
.parse = ParserType.wrap_parse,
|
||||||
.finish = ParserType.wrap_finish,
|
.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
|
// the parser is generated by the bind method of the CommandBuilder, so we can
|
||||||
// be extremely type-sloppy here, which simplifies the signature.
|
// be extremely type-sloppy here, which simplifies the signature.
|
||||||
pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
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,
|
progname: ?[]const u8 = null,
|
||||||
has_global_tags: bool = false,
|
has_global_tags: bool = false,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
subcommands: std.hash_map.StringHashMap(ParserInterface),
|
subcommands: CommandMap,
|
||||||
subcommand: ?ParserInterface = null,
|
subcommand: ?ParserInterface = null,
|
||||||
help_builder: help.HelpBuilder(command),
|
help_builder: help.HelpBuilder(command),
|
||||||
|
|
||||||
@ -98,13 +107,13 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
return try self.execute(context);
|
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 self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), parser));
|
||||||
const context = if (@alignOf(UserContext) > 0)
|
const context = if (@alignOf(UserContext) > 0)
|
||||||
@ptrCast(*UserContext, @alignCast(@alignOf(UserContext), ctx))
|
@ptrCast(*UserContext, @alignCast(@alignOf(UserContext), ctx))
|
||||||
else
|
else
|
||||||
@ptrCast(*UserContext, ctx);
|
@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 {
|
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);
|
return try self.finish(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subparse(self: *@This(), context: *UserContext, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
fn describe() []const u8 {
|
||||||
const sliceto = try self.parse(args);
|
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.read_environment(env);
|
||||||
try self.convert_eager(context);
|
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 {
|
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;
|
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);
|
try self.finish(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +178,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
|
|
||||||
pub fn parse(
|
pub fn parse(
|
||||||
self: *@This(),
|
self: *@This(),
|
||||||
|
name: []const u8,
|
||||||
args: [][:0]u8,
|
args: [][:0]u8,
|
||||||
) anyerror!usize {
|
) anyerror!usize {
|
||||||
// run pre-parse pass if we have any global parameters
|
// 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 (!forced_ordinal and arg.len > 1 and arg[0] == '-') {
|
||||||
if (arg.len > 2 and arg[1] == '-') {
|
if (arg.len > 2 and arg[1] == '-') {
|
||||||
try self.parse_long_tag(arg, &argit);
|
try self.parse_long_tag(name, arg, &argit);
|
||||||
continue :argloop;
|
continue :argloop;
|
||||||
} else if (arg.len > 1) {
|
} else if (arg.len > 1) {
|
||||||
for (arg[1..], 1..) |short, idx| {
|
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;
|
continue :argloop;
|
||||||
}
|
}
|
||||||
@ -227,12 +248,13 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
|
|
||||||
inline fn parse_long_tag(
|
inline fn parse_long_tag(
|
||||||
self: *@This(),
|
self: *@This(),
|
||||||
|
name: []const u8,
|
||||||
arg: []const u8,
|
arg: []const u8,
|
||||||
argit: *ncmeta.SliceIterator([][:0]u8),
|
argit: *ncmeta.SliceIterator([][:0]u8),
|
||||||
) ParseError!void {
|
) ParseError!void {
|
||||||
if (comptime command.help_flag.long_tag) |long|
|
if (comptime command.help_flag.long_tag) |long|
|
||||||
if (std.mem.eql(u8, arg, long))
|
if (std.mem.eql(u8, arg, long))
|
||||||
self.print_help();
|
self.print_help(name);
|
||||||
|
|
||||||
inline for (comptime parameters) |param| {
|
inline for (comptime parameters) |param| {
|
||||||
const PType = @TypeOf(param);
|
const PType = @TypeOf(param);
|
||||||
@ -256,13 +278,14 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
|
|
||||||
inline fn parse_short_tag(
|
inline fn parse_short_tag(
|
||||||
self: *@This(),
|
self: *@This(),
|
||||||
|
name: []const u8,
|
||||||
arg: u8,
|
arg: u8,
|
||||||
remaining: usize,
|
remaining: usize,
|
||||||
argit: *ncmeta.SliceIterator([][:0]u8),
|
argit: *ncmeta.SliceIterator([][:0]u8),
|
||||||
) ParseError!void {
|
) ParseError!void {
|
||||||
if (comptime command.help_flag.short_tag) |short|
|
if (comptime command.help_flag.short_tag) |short|
|
||||||
if (arg == short[1])
|
if (arg == short[1])
|
||||||
self.print_help();
|
self.print_help(name);
|
||||||
|
|
||||||
inline for (comptime parameters) |param| {
|
inline for (comptime parameters) |param| {
|
||||||
const PType = @TypeOf(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);
|
defer std.process.exit(0);
|
||||||
|
|
||||||
const stderr = std.io.getStdErr().writer();
|
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
|
stderr.writeAll(message) catch return
|
||||||
else |_|
|
else |_|
|
||||||
stderr.writeAll("There was a problem generating the help.") catch return;
|
stderr.writeAll("There was a problem generating the help.") catch return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user