Compare commits
14 Commits
80c4853171
...
zig-0.11.x
Author | SHA1 | Date | |
---|---|---|---|
0d091611dd
|
|||
5f45290423
|
|||
b77a1f59c2
|
|||
03a4404a17
|
|||
6e1199afa9
|
|||
70c6cea591
|
|||
645ef24a4a
|
|||
768a81e2bd
|
|||
35915191fb
|
|||
76e8dedf14
|
|||
8ac610ae71
|
|||
8bba68e5a9
|
|||
390a1ba4fd
|
|||
0695743a1f
|
@@ -8,9 +8,22 @@ const Choice = enum { first, second };
|
||||
const cli = cmd: {
|
||||
var cmd = CommandBuilder(*u32){
|
||||
.description =
|
||||
\\The definitive noclip demonstration utility
|
||||
\\The definitive noclip demonstration utility.
|
||||
\\
|
||||
\\This command demonstrates the functionality of the noclip library. cool!
|
||||
\\
|
||||
\\> // implementing factorial recursively is a silly thing to do
|
||||
\\> pub fn fact(n: u64) u64 {
|
||||
\\> if (n == 0) return 1;
|
||||
\\> return n*fact(n - 1);
|
||||
\\> }
|
||||
\\
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
|
||||
\\eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
||||
\\sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
,
|
||||
};
|
||||
cmd.addOption(.{ .OutputType = struct { u8, u8 } }, .{
|
||||
@@ -30,6 +43,13 @@ const cli = cmd: {
|
||||
.description = "enum choice option",
|
||||
.nice_type_name = "choice",
|
||||
});
|
||||
cmd.stringOption(.{
|
||||
.name = "string",
|
||||
.short_tag = "-s",
|
||||
.long_tag = "--string",
|
||||
.env_var = "NOCLIP_STRING",
|
||||
.description = "A string value option",
|
||||
});
|
||||
cmd.addOption(.{ .OutputType = u32 }, .{
|
||||
.name = "default",
|
||||
.short_tag = "-d",
|
||||
@@ -80,8 +100,8 @@ const subcommand = cmd: {
|
||||
.falsy = .{ .long_tag = "--no-flag" },
|
||||
.env_var = "NOCLIP_SUBFLAG",
|
||||
});
|
||||
cmd.addArgument(.{ .OutputType = []const u8 }, .{ .name = "argument" });
|
||||
cmd.addArgument(.{ .OutputType = []const u8 }, .{
|
||||
cmd.stringArgument(.{ .name = "argument" });
|
||||
cmd.stringArgument(.{
|
||||
.name = "arg",
|
||||
.description = "This is an argument that doesn't really do anything, but it's very important.",
|
||||
});
|
||||
@@ -95,6 +115,7 @@ fn subHandler(context: []const u8, result: subcommand.Output()) !void {
|
||||
|
||||
fn cliHandler(context: *u32, result: cli.Output()) !void {
|
||||
std.debug.print("context: {d}\n", .{context.*});
|
||||
std.debug.print("callback is working {s}\n", .{result.string orelse "null"});
|
||||
std.debug.print("callback is working {any}\n", .{result.choice});
|
||||
std.debug.print("callback is working {d}\n", .{result.default});
|
||||
context.* += 1;
|
||||
@@ -105,17 +126,29 @@ pub fn main() !u8 {
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var parser = try cli.createParser(cliHandler, allocator);
|
||||
defer parser.deinitTree();
|
||||
const base = try noclip.commandGroup(allocator, .{ .description = "base group" });
|
||||
defer base.deinitTree();
|
||||
|
||||
var context: u32 = 2;
|
||||
const sc: []const u8 = "whassup";
|
||||
|
||||
var subcon = try subcommand.createParser(subHandler, allocator);
|
||||
try parser.addSubcommand("verb", subcon.interface(&sc));
|
||||
try base.addSubcommand("main", try cli.createInterface(allocator, cliHandler, &context));
|
||||
try base.addSubcommand("other", try subcommand.createInterface(allocator, subHandler, &sc));
|
||||
|
||||
const iface = parser.interface(&context);
|
||||
iface.execute() catch return 1;
|
||||
const group = try noclip.commandGroup(allocator, .{ .description = "final level of a deeply nested subcommand" });
|
||||
const subcon = try noclip.commandGroup(allocator, .{ .description = "third level of a deeply nested subcommand" });
|
||||
const nested = try noclip.commandGroup(allocator, .{ .description = "second level of a deeply nested subcommand" });
|
||||
const deeply = try noclip.commandGroup(allocator, .{ .description = "start of a deeply nested subcommand" });
|
||||
try base.addSubcommand("deeply", deeply);
|
||||
try deeply.addSubcommand("nested", nested);
|
||||
try nested.addSubcommand("subcommand", subcon);
|
||||
try subcon.addSubcommand("group", group);
|
||||
try group.addSubcommand("run", try cli.createInterface(allocator, cliHandler, &context));
|
||||
|
||||
base.execute() catch |err| {
|
||||
std.io.getStdErr().writeAll(base.getParseError()) catch {};
|
||||
return err;
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -72,6 +72,76 @@ fn BuilderGenerics(comptime UserContext: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub const GroupOptions = struct {
|
||||
help_flag: ShortLongPair = .{ .short_tag = "-h", .long_tag = "--help" },
|
||||
description: []const u8,
|
||||
};
|
||||
|
||||
pub fn commandGroup(allocator: std.mem.Allocator, comptime options: GroupOptions) !ParserInterface {
|
||||
const cmd = comptime CommandBuilder(void){
|
||||
.help_flag = options.help_flag,
|
||||
.description = options.description,
|
||||
.subcommand_required = true,
|
||||
};
|
||||
|
||||
return try cmd.createInterface(allocator, cmd.noopCallback());
|
||||
}
|
||||
|
||||
fn InterfaceCreator(comptime Command: type) type {
|
||||
return if (Command.ICC.InputType()) |Type|
|
||||
struct {
|
||||
pub fn createInterface(
|
||||
comptime self: Command,
|
||||
allocator: std.mem.Allocator,
|
||||
comptime callback: self.CallbackSignature(),
|
||||
context: Type,
|
||||
) !ParserInterface {
|
||||
return try self._createInterfaceImpl(allocator, callback, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
struct {
|
||||
pub fn createInterface(
|
||||
comptime self: Command,
|
||||
allocator: std.mem.Allocator,
|
||||
comptime callback: self.CallbackSignature(),
|
||||
) !ParserInterface {
|
||||
return try self._createInterfaceImpl(allocator, callback, void{});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const InterfaceContextCategory = union(enum) {
|
||||
empty,
|
||||
pointer: type,
|
||||
value: type,
|
||||
|
||||
pub fn fromType(comptime ContextType: type) InterfaceContextCategory {
|
||||
return switch (@typeInfo(ContextType)) {
|
||||
.Void => .empty,
|
||||
.Pointer => |info| if (info.size == .Slice) .{ .value = ContextType } else .{ .pointer = ContextType },
|
||||
// technically, i0, u0, and struct{} should be treated as empty, probably
|
||||
else => .{ .value = ContextType },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn InputType(comptime self: InterfaceContextCategory) ?type {
|
||||
return switch (self) {
|
||||
.empty => null,
|
||||
.pointer => |Type| Type,
|
||||
.value => |Type| *const Type,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn OutputType(comptime self: InterfaceContextCategory) type {
|
||||
return switch (self) {
|
||||
.empty => void,
|
||||
.pointer => |Type| Type,
|
||||
.value => |Type| Type,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
return struct {
|
||||
param_spec: ncmeta.TupleBuilder = .{},
|
||||
@@ -82,6 +152,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
description: []const u8,
|
||||
|
||||
pub const UserContextType = UserContext;
|
||||
pub const ICC: InterfaceContextCategory = InterfaceContextCategory.fromType(UserContextType);
|
||||
|
||||
pub fn createParser(
|
||||
comptime self: @This(),
|
||||
@@ -96,16 +167,18 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
return Parser(self, callback){
|
||||
.arena = arena,
|
||||
.allocator = arena_alloc,
|
||||
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(arena_alloc),
|
||||
.subcommands = parser.CommandMap.init(arena_alloc),
|
||||
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createInterface(
|
||||
pub usingnamespace InterfaceCreator(@This());
|
||||
|
||||
fn _createInterfaceImpl(
|
||||
comptime self: @This(),
|
||||
comptime callback: self.CallbackSignature(),
|
||||
allocator: std.mem.Allocator,
|
||||
context: UserContextType,
|
||||
comptime callback: self.CallbackSignature(),
|
||||
context: (ICC.InputType() orelse void),
|
||||
) !ParserInterface {
|
||||
var arena = try allocator.create(std.heap.ArenaAllocator);
|
||||
arena.* = std.heap.ArenaAllocator.init(allocator);
|
||||
@@ -115,11 +188,11 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
this_parser.* = .{
|
||||
.arena = arena,
|
||||
.allocator = arena_alloc,
|
||||
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(arena_alloc),
|
||||
.subcommands = parser.CommandMap.init(arena_alloc),
|
||||
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
||||
};
|
||||
|
||||
if (UserContextType == void) {
|
||||
if (comptime ICC == .empty) {
|
||||
return this_parser.interface();
|
||||
} else {
|
||||
return this_parser.interface(context);
|
||||
@@ -133,7 +206,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
self.help_flag = tags;
|
||||
}
|
||||
|
||||
const string_generics = BuilderGenerics(UserContext){ .OutputType = []const u8 };
|
||||
const string_generics = BuilderGenerics(UserContext){ .OutputType = [:0]const u8 };
|
||||
|
||||
pub fn stringOption(
|
||||
comptime self: *@This(),
|
||||
@@ -271,8 +344,14 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
return self.param_spec.realTuple();
|
||||
}
|
||||
|
||||
pub fn noopCallback(comptime self: @This()) self.CallbackSignature() {
|
||||
return struct {
|
||||
fn callback(_: UserContextType, _: self.Output()) !void {}
|
||||
}.callback;
|
||||
}
|
||||
|
||||
pub fn CallbackSignature(comptime self: @This()) type {
|
||||
return *const fn (UserContext, self.Output()) anyerror!void;
|
||||
return *const fn (UserContextType, self.Output()) anyerror!void;
|
||||
}
|
||||
|
||||
pub fn Output(comptime self: @This()) type {
|
||||
@@ -293,7 +372,9 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
// global tags and env_vars would conflict, which is less common.
|
||||
if (param.short_tag) |short|
|
||||
tag_fields = tag_fields ++ &[_]StructField{.{
|
||||
.name = short,
|
||||
// this goofy construct coerces the comptime []const u8 to
|
||||
// [:0]const u8.
|
||||
.name = short ++ "",
|
||||
.type = void,
|
||||
.default_value = null,
|
||||
.is_comptime = false,
|
||||
@@ -302,7 +383,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
|
||||
if (param.long_tag) |long|
|
||||
tag_fields = tag_fields ++ &[_]StructField{.{
|
||||
.name = long,
|
||||
.name = long ++ "",
|
||||
.type = void,
|
||||
.default_value = null,
|
||||
.is_comptime = false,
|
||||
@@ -311,7 +392,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
|
||||
if (param.env_var) |env_var|
|
||||
env_var_fields = env_var_fields ++ &[_]StructField{.{
|
||||
.name = env_var,
|
||||
.name = env_var ++ "",
|
||||
.type = void,
|
||||
.default_value = null,
|
||||
.is_comptime = false,
|
||||
@@ -356,7 +437,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
const default = if (param.default) |def| &@as(FieldType, def) else @as(?*const anyopaque, null);
|
||||
|
||||
fields = fields ++ &[_]StructField{.{
|
||||
.name = param.name,
|
||||
.name = param.name ++ "",
|
||||
.type = FieldType,
|
||||
.default_value = @ptrCast(default),
|
||||
.is_comptime = false,
|
||||
@@ -426,7 +507,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
||||
?PType.G.IntermediateType();
|
||||
|
||||
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
|
||||
.name = param.name,
|
||||
.name = param.name ++ "",
|
||||
.type = FieldType,
|
||||
.default_value = @ptrCast(&@as(
|
||||
FieldType,
|
||||
|
@@ -61,7 +61,7 @@ fn MultiConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
|
||||
|
||||
fn FlagConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
||||
return struct {
|
||||
pub fn handler(_: gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError!bool {
|
||||
pub fn handler(_: gen.UserContext, input: [:0]const u8, _: ErrorWriter) ConversionError!bool {
|
||||
// treat an empty string as falsy
|
||||
if (input.len == 0) return false;
|
||||
|
||||
@@ -81,7 +81,7 @@ fn FlagConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
||||
|
||||
fn StringConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
||||
return struct {
|
||||
pub fn handler(_: gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError![]const u8 {
|
||||
pub fn handler(_: gen.UserContext, input: [:0]const u8, _: ErrorWriter) ConversionError![:0]const u8 {
|
||||
return input;
|
||||
}
|
||||
}.handler;
|
||||
@@ -91,7 +91,7 @@ fn IntConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
||||
const IntType = gen.OutputType;
|
||||
|
||||
return struct {
|
||||
pub fn handler(_: gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!IntType {
|
||||
pub fn handler(_: gen.UserContext, input: [:0]const u8, failure: ErrorWriter) ConversionError!IntType {
|
||||
return std.fmt.parseInt(IntType, input, 0) catch {
|
||||
try failure.print("cannot interpret \"{s}\" as an integer", .{input});
|
||||
return ConversionError.ConversionFailed;
|
||||
@@ -137,7 +137,7 @@ fn ChoiceConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
||||
const EnumType = gen.OutputType;
|
||||
|
||||
return struct {
|
||||
pub fn handler(_: gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!EnumType {
|
||||
pub fn handler(_: gen.UserContext, input: [:0]const u8, failure: ErrorWriter) ConversionError!EnumType {
|
||||
return std.meta.stringToEnum(gen.ConvertedType(), input) orelse {
|
||||
try failure.print("\"{s}\" is not a valid choice", .{input});
|
||||
return ConversionError.ConversionFailed;
|
||||
|
@@ -12,6 +12,7 @@ pub const ParseError = error{
|
||||
UnknownLongTagParameter,
|
||||
UnknownShortTagParameter,
|
||||
RequiredParameterMissing,
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const NoclipError = ParseError || ConversionError;
|
||||
|
@@ -53,6 +53,8 @@ pub fn StructuredPrinter(comptime Writer: type) type {
|
||||
// TODO: lol return a real error
|
||||
if (indent >= self.wrap_width) return NoclipError.UnexpectedFailure;
|
||||
|
||||
if (text.len == 0) return;
|
||||
|
||||
// this assumes output stream has already had the first line properly
|
||||
// indented.
|
||||
var splitter = std.mem.split(u8, text, "\n");
|
||||
@@ -63,6 +65,17 @@ pub fn StructuredPrinter(comptime Writer: type) type {
|
||||
// we have a trailing line that needs to be cleaned up
|
||||
if (location > indent)
|
||||
_ = try self.clearLine(indent);
|
||||
|
||||
location = try self.clearLine(indent);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '>') maybe: {
|
||||
if (line.len > 1) {
|
||||
if (line[1] == ' ') {
|
||||
try self.writer.writeAll(line[2..]);
|
||||
} else break :maybe;
|
||||
}
|
||||
location = try self.clearLine(indent);
|
||||
continue;
|
||||
}
|
||||
@@ -99,6 +112,7 @@ pub fn StructuredPrinter(comptime Writer: type) type {
|
||||
}
|
||||
if (location > indent)
|
||||
try self.writer.writeByte(' ');
|
||||
|
||||
try self.writer.writeAll(choppee[0..split]);
|
||||
location = try self.clearLine(indent);
|
||||
choppee = choppee[split + 1 ..];
|
||||
@@ -227,8 +241,6 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
||||
|
||||
var just: usize = 0;
|
||||
inline for (comptime help_info.arguments) |arg| {
|
||||
if (comptime arg.description.len == 0) continue;
|
||||
|
||||
const pair: AlignablePair = .{
|
||||
.left = arg.name,
|
||||
.right = arg.description,
|
||||
@@ -348,11 +360,10 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
||||
defer pairs.deinit();
|
||||
|
||||
var just: usize = 0;
|
||||
var iter = subcommands.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
for (subcommands.keys()) |key| {
|
||||
const pair: AlignablePair = .{
|
||||
.left = key.*,
|
||||
.right = subcommands.get(key.*).?.describe(),
|
||||
.left = key,
|
||||
.right = subcommands.get(key).?.describe(),
|
||||
};
|
||||
if (pair.left.len > just) just = pair.left.len;
|
||||
try pairs.append(pair);
|
||||
|
@@ -174,10 +174,43 @@ pub fn SliceIterator(comptime T: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn MutatingZSplitter(comptime T: type) type {
|
||||
return struct {
|
||||
buffer: [:0]T,
|
||||
delimiter: T,
|
||||
index: ?usize = 0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Returns a slice of the next field, or null if splitting is complete.
|
||||
pub fn next(self: *Self) ?[:0]T {
|
||||
const start = self.index orelse return null;
|
||||
|
||||
const end = if (std.mem.indexOfScalarPos(T, self.buffer, start, self.delimiter)) |delim_idx| blk: {
|
||||
self.buffer[delim_idx] = 0;
|
||||
self.index = delim_idx + 1;
|
||||
break :blk delim_idx;
|
||||
} else blk: {
|
||||
self.index = null;
|
||||
break :blk self.buffer.len;
|
||||
};
|
||||
|
||||
return self.buffer[start..end :0];
|
||||
}
|
||||
|
||||
/// Returns a slice of the remaining bytes. Does not affect iterator state.
|
||||
pub fn rest(self: Self) [:0]T {
|
||||
const end = self.buffer.len;
|
||||
const start = self.index orelse end;
|
||||
return self.buffer[start..end :0];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn copyStruct(comptime T: type, source: T, field_overrides: anytype) T {
|
||||
var result: T = undefined;
|
||||
|
||||
comptime inline for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
|
||||
comptime for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
|
||||
if (!@hasField(T, field.name)) @compileError("override contains bad field" ++ field);
|
||||
};
|
||||
|
||||
@@ -221,9 +254,8 @@ pub const TupleBuilder = struct {
|
||||
comptime {
|
||||
var fields: [self.types.len]StructField = undefined;
|
||||
for (self.types, 0..) |Type, idx| {
|
||||
var num_buf: [128]u8 = undefined;
|
||||
fields[idx] = .{
|
||||
.name = std.fmt.bufPrint(&num_buf, "{d}", .{idx}) catch @compileError("failed to write field"),
|
||||
.name = std.fmt.comptimePrint("{d}", .{idx}),
|
||||
.type = Type,
|
||||
.default_value = null,
|
||||
// TODO: is this the right thing to do?
|
||||
|
@@ -7,4 +7,5 @@ pub const parameters = @import("./parameters.zig");
|
||||
pub const parser = @import("./parser.zig");
|
||||
|
||||
pub const CommandBuilder = command.CommandBuilder;
|
||||
pub const commandGroup = command.commandGroup;
|
||||
pub const ParserInterface = parser.ParserInterface;
|
||||
|
@@ -18,7 +18,7 @@ pub const FlagBias = enum {
|
||||
truthy,
|
||||
unbiased,
|
||||
|
||||
pub fn string(comptime self: @This()) []const u8 {
|
||||
pub fn string(comptime self: @This()) [:0]const u8 {
|
||||
return switch (comptime self) {
|
||||
.truthy => "true",
|
||||
.falsy => "false",
|
||||
@@ -110,12 +110,12 @@ pub const ParameterGenerics = struct {
|
||||
|
||||
pub fn IntermediateValue(comptime self: @This()) type {
|
||||
return comptime switch (self.value_count) {
|
||||
.flag => []const u8,
|
||||
.flag => [:0]const u8,
|
||||
.count => usize,
|
||||
.fixed => |count| switch (count) {
|
||||
0 => @compileError("bad fixed-zero parameter"),
|
||||
1 => []const u8,
|
||||
else => std.ArrayList([]const u8),
|
||||
1 => [:0]const u8,
|
||||
else => std.ArrayList([:0]const u8),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -223,6 +223,26 @@ fn OptionType(comptime generics: ParameterGenerics) type {
|
||||
/// want weird things to happen.
|
||||
flag_bias: FlagBias,
|
||||
|
||||
pub fn describe(self: @This(), allocator: std.mem.Allocator) std.mem.Allocator.Error![]const u8 {
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
|
||||
try buf.append('"');
|
||||
try buf.appendSlice(self.name);
|
||||
try buf.append('"');
|
||||
if (self.short_tag != null or self.long_tag != null) {
|
||||
try buf.appendSlice(" (");
|
||||
if (self.short_tag) |short|
|
||||
try buf.appendSlice(short);
|
||||
if (self.short_tag != null and self.long_tag != null)
|
||||
try buf.appendSlice(", ");
|
||||
if (self.long_tag) |long|
|
||||
try buf.appendSlice(long);
|
||||
try buf.append(')');
|
||||
}
|
||||
|
||||
return try buf.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn IntermediateValue(comptime _: @This()) type {
|
||||
return generics.IntermediateValue();
|
||||
}
|
||||
|
@@ -10,9 +10,11 @@ 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, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void,
|
||||
finish: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
|
||||
getChild: *const fn (parser: *anyopaque, name: []const u8) ?ParserInterface,
|
||||
parse: *const fn (parser: *anyopaque, context: *anyopaque, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!?ParseResult,
|
||||
finish: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!?ParserInterface,
|
||||
getParseError: *const fn (parser: *anyopaque) []const u8,
|
||||
addSubcommand: *const fn (parser: *anyopaque, name: []const u8, subcommand: ParserInterface) std.mem.Allocator.Error!void,
|
||||
getSubcommand: *const fn (parser: *anyopaque, name: []const u8) ?ParserInterface,
|
||||
describe: *const fn () []const u8,
|
||||
deinit: *const fn (parser: *anyopaque) void,
|
||||
deinitTree: *const fn (parser: *anyopaque) void,
|
||||
@@ -30,7 +32,9 @@ pub const ParserInterface = struct {
|
||||
.execute = ParserType._wrapExecute,
|
||||
.parse = ParserType._wrapParse,
|
||||
.finish = ParserType._wrapFinish,
|
||||
.getChild = ParserType._wrapGetChild,
|
||||
.getParseError = ParserType._wrapGetParseError,
|
||||
.addSubcommand = ParserType._wrapAddSubcommand,
|
||||
.getSubcommand = ParserType._wrapGetSubcommand,
|
||||
.describe = ParserType._wrapDescribe,
|
||||
.deinit = ParserType._wrapDeinit,
|
||||
.deinitTree = ParserType._wrapDeinitTree,
|
||||
@@ -42,16 +46,24 @@ pub const ParserInterface = struct {
|
||||
return try self.methods.execute(self.parser, self.context);
|
||||
}
|
||||
|
||||
pub fn parse(self: @This(), name: []const u8, 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!?ParseResult {
|
||||
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!?ParserInterface {
|
||||
return try self.methods.finish(self.parser, self.context);
|
||||
}
|
||||
|
||||
pub fn getChild(self: @This(), name: []const u8) ?ParserInterface {
|
||||
return self.methods.getChild(self.parser, name);
|
||||
pub fn getParseError(self: @This()) []const u8 {
|
||||
return self.methods.getParseError(self.parser);
|
||||
}
|
||||
|
||||
pub fn addSubcommand(self: @This(), name: []const u8, subcommand: ParserInterface) std.mem.Allocator.Error!void {
|
||||
return try self.methods.addSubcommand(self.parser, name, subcommand);
|
||||
}
|
||||
|
||||
pub fn getSubcommand(self: @This(), name: []const u8) ?ParserInterface {
|
||||
return self.methods.getSubcommand(self.parser, name);
|
||||
}
|
||||
|
||||
pub fn describe(self: @This()) []const u8 {
|
||||
@@ -67,89 +79,8 @@ pub const ParserInterface = struct {
|
||||
}
|
||||
};
|
||||
|
||||
fn InterfaceWrappers(comptime ParserType: type) type {
|
||||
return struct {
|
||||
inline fn castInterfaceParser(parser: *anyopaque) *ParserType {
|
||||
return @ptrCast(@alignCast(parser));
|
||||
}
|
||||
|
||||
fn _wrapExecute(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
|
||||
const self = castInterfaceParser(parser);
|
||||
|
||||
const context = self.castContext(ctx);
|
||||
return try self.execute(context);
|
||||
}
|
||||
|
||||
fn _wrapParse(
|
||||
parser: *anyopaque,
|
||||
ctx: *anyopaque,
|
||||
name: []const u8,
|
||||
args: [][:0]u8,
|
||||
env: std.process.EnvMap,
|
||||
) anyerror!void {
|
||||
const self = castInterfaceParser(parser);
|
||||
const context = self.castContext(ctx);
|
||||
return try self.subparse(context, name, args, env);
|
||||
}
|
||||
|
||||
fn _wrapFinish(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
|
||||
const self = castInterfaceParser(parser);
|
||||
const context = self.castContext(ctx);
|
||||
return try self.finish(context);
|
||||
}
|
||||
|
||||
fn _wrapGetChild(parser: *anyopaque, name: []const u8) ?ParserInterface {
|
||||
const self = castInterfaceParser(parser);
|
||||
return self.getChild(name);
|
||||
}
|
||||
|
||||
fn _wrapDeinit(parser: *anyopaque) void {
|
||||
const self = castInterfaceParser(parser);
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
fn _wrapDeinitTree(parser: *anyopaque) void {
|
||||
const self = castInterfaceParser(parser);
|
||||
self.deinitTree();
|
||||
}
|
||||
|
||||
fn _wrapDescribe() []const u8 {
|
||||
return ParserType.command_description;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
|
||||
const CtxInfo = @typeInfo(UserContext);
|
||||
|
||||
return if (CtxInfo == .Void) struct {
|
||||
pub fn interface(self: *ParserType) ParserInterface {
|
||||
return ParserInterface.create(ParserType, self, @constCast(&void{}));
|
||||
}
|
||||
|
||||
fn castContext(_: ParserType, _: *anyopaque) void {
|
||||
return void{};
|
||||
}
|
||||
} else if (CtxInfo == .Pointer and CtxInfo.Pointer.size != .Slice) struct {
|
||||
pub fn interface(self: *ParserType, context: UserContext) ParserInterface {
|
||||
return ParserInterface.create(ParserType, self, @constCast(context));
|
||||
}
|
||||
|
||||
fn castContext(_: ParserType, ctx: *anyopaque) UserContext {
|
||||
return @ptrCast(@alignCast(ctx));
|
||||
}
|
||||
} else struct {
|
||||
pub fn interface(self: *ParserType, context: *const UserContext) ParserInterface {
|
||||
return ParserInterface.create(ParserType, self, @ptrCast(@constCast(context)));
|
||||
}
|
||||
|
||||
fn castContext(_: ParserType, ctx: *anyopaque) UserContext {
|
||||
return @as(*const UserContext, @ptrCast(@alignCast(ctx))).*;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const CommandMap = std.hash_map.StringHashMap(ParserInterface);
|
||||
pub const CommandMap = std.StringArrayHashMap(ParserInterface);
|
||||
const ParseResult = struct { name: []const u8, args: [][:0]u8, parser: ParserInterface };
|
||||
|
||||
// the parser is generated by the bind method of the CommandBuilder, so we can
|
||||
// be extremely type-sloppy here, which simplifies the signature.
|
||||
@@ -171,41 +102,103 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
allocator: std.mem.Allocator,
|
||||
subcommands: CommandMap,
|
||||
subcommand: ?ParserInterface = null,
|
||||
error_message: std.ArrayListUnmanaged(u8) = .{},
|
||||
help_builder: help.HelpBuilder(command),
|
||||
|
||||
pub fn addSubcommand(self: *@This(), verb: []const u8, parser: ParserInterface) !void {
|
||||
try self.subcommands.put(verb, parser);
|
||||
}
|
||||
|
||||
// This is a slightly annoying hack to work around the fact that there's no way to
|
||||
// provide a method signature conditionally.
|
||||
pub usingnamespace InterfaceGen(@This(), UserContext);
|
||||
// This is a slightly annoying hack to work around the fact that there's no way
|
||||
// to provide a method signature conditionally.
|
||||
pub usingnamespace InterfaceGen(@This(), @TypeOf(command).ICC);
|
||||
// This is attached to the struct this way because these are all "private"
|
||||
// methods that exist exclusively to cast the type-erased interface object back
|
||||
// into something usable. Their implementations aren't meaningful and just
|
||||
// cognitively clutter this struct.
|
||||
pub usingnamespace InterfaceWrappers(@This());
|
||||
|
||||
pub fn subparse(self: *@This(), context: UserContext, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
|
||||
pub fn execute(self: *@This(), context: UserContext) anyerror!void {
|
||||
const args = std.process.argsAlloc(self.allocator) catch |err| {
|
||||
try self.error_message.appendSlice(
|
||||
self.allocator,
|
||||
"Failed to allocate process arg vector\n",
|
||||
);
|
||||
return err;
|
||||
};
|
||||
const env = std.process.getEnvMap(self.allocator) catch |err| {
|
||||
try self.error_message.appendSlice(
|
||||
self.allocator,
|
||||
"Failed to allocate process environment variable map\n",
|
||||
);
|
||||
return err;
|
||||
};
|
||||
|
||||
if (args.len < 1) {
|
||||
try self.error_message.appendSlice(
|
||||
self.allocator,
|
||||
"The argument list for the base CLI entry point is empty.\n",
|
||||
);
|
||||
return ParseError.EmptyArgs;
|
||||
}
|
||||
|
||||
self.progname = std.fs.path.basename(args[0]);
|
||||
|
||||
{
|
||||
var subc = try self.subparse(context, self.progname.?, args[1..], env);
|
||||
while (subc) |next| {
|
||||
subc = next.parser.parse(next.name, next.args, env) catch |err| {
|
||||
try self.error_message.appendSlice(self.allocator, next.parser.getParseError());
|
||||
return err;
|
||||
};
|
||||
}
|
||||
}
|
||||
{
|
||||
var subc = try self.finish(context);
|
||||
while (subc) |next| {
|
||||
subc = next.finish() catch |err| {
|
||||
try self.error_message.appendSlice(self.allocator, next.getParseError());
|
||||
return err;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subparse(
|
||||
self: *@This(),
|
||||
context: UserContext,
|
||||
name: []const u8,
|
||||
args: [][:0]u8,
|
||||
env: std.process.EnvMap,
|
||||
) anyerror!?ParseResult {
|
||||
const sliceto = try self.parse(name, args);
|
||||
try self.readEnvironment(env);
|
||||
try self.convertEager(context);
|
||||
|
||||
if (self.subcommand) |verb| {
|
||||
const verbname = try std.mem.join(
|
||||
if (self.subcommand) |subcommand| {
|
||||
const grafted_name = try std.mem.join(
|
||||
self.allocator,
|
||||
" ",
|
||||
&[_][]const u8{ name, args[sliceto - 1] },
|
||||
&.{ name, args[sliceto - 1] },
|
||||
);
|
||||
try verb.parse(verbname, args[sliceto..], env);
|
||||
return .{ .name = grafted_name, .args = args[sliceto..], .parser = subcommand };
|
||||
} else if (self.subcommands.count() > 0 and command.subcommand_required) {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.writeAll("A subcommand is required.\n\n");
|
||||
try stderr.print("'{s}' requires a subcommand.\n\n", .{name});
|
||||
|
||||
self.printHelp(name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn finish(self: *@This(), context: UserContext) anyerror!void {
|
||||
pub fn finish(self: *@This(), context: UserContext) anyerror!?ParserInterface {
|
||||
try self.convert(context);
|
||||
try callback(context, self.output);
|
||||
if (self.subcommand) |verb| try verb.finish();
|
||||
return self.subcommand;
|
||||
}
|
||||
|
||||
pub fn getParseError(self: @This()) []const u8 {
|
||||
return if (self.error_message.items.len == 0)
|
||||
"An unexpected error occurred.\n"
|
||||
else
|
||||
self.error_message.items;
|
||||
}
|
||||
|
||||
pub fn deinit(self: @This()) void {
|
||||
@@ -214,27 +207,18 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
}
|
||||
|
||||
pub fn deinitTree(self: @This()) void {
|
||||
var iterator = self.subcommands.valueIterator();
|
||||
while (iterator.next()) |subcommand| {
|
||||
for (self.subcommands.values()) |subcommand| {
|
||||
subcommand.deinitTree();
|
||||
}
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
pub fn getChild(self: @This(), name: []const u8) ?ParserInterface {
|
||||
return self.subcommands.get(name);
|
||||
pub fn addSubcommand(self: *@This(), name: []const u8, parser: ParserInterface) !void {
|
||||
try self.subcommands.put(name, parser);
|
||||
}
|
||||
|
||||
pub fn execute(self: *@This(), context: UserContext) anyerror!void {
|
||||
const args = try std.process.argsAlloc(self.allocator);
|
||||
var env = try std.process.getEnvMap(self.allocator);
|
||||
|
||||
if (args.len < 1) return ParseError.EmptyArgs;
|
||||
|
||||
self.progname = std.fs.path.basename(args[0]);
|
||||
|
||||
try self.subparse(context, self.progname.?, args[1..], env);
|
||||
try self.finish(context);
|
||||
pub fn getSubcommand(self: @This(), name: []const u8) ?ParserInterface {
|
||||
return self.subcommands.get(name);
|
||||
}
|
||||
|
||||
fn printValue(self: @This(), value: anytype, comptime indent: []const u8) void {
|
||||
@@ -309,8 +293,8 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
forced_ordinal = true;
|
||||
}
|
||||
|
||||
if (try self.parseOrdinals(arg, &argit)) |verb| {
|
||||
self.subcommand = verb;
|
||||
if (try self.parseOrdinals(arg, &argit)) |subcommand| {
|
||||
self.subcommand = subcommand;
|
||||
// TODO: return slice of remaining or offset index
|
||||
return argit.index;
|
||||
}
|
||||
@@ -322,7 +306,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
fn parseLongTag(
|
||||
self: *@This(),
|
||||
name: []const u8,
|
||||
arg: []const u8,
|
||||
arg: [:0]u8,
|
||||
argit: *ncmeta.SliceIterator([][:0]u8),
|
||||
) ParseError!void {
|
||||
if (comptime command.help_flag.long_tag) |long|
|
||||
@@ -346,6 +330,10 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
}
|
||||
}
|
||||
|
||||
try self.error_message.writer(self.allocator).print(
|
||||
"Could not parse command line: unknown option \"{s}\"\n",
|
||||
.{arg},
|
||||
);
|
||||
return ParseError.UnknownLongTagParameter;
|
||||
}
|
||||
|
||||
@@ -368,20 +356,29 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
if (arg == tag[1]) {
|
||||
if (comptime !PType.is_flag)
|
||||
if (remaining > 0)
|
||||
if (remaining > 0) {
|
||||
try self.error_message.writer(self.allocator).print(
|
||||
"Could not parse command line: \"-{c}\" is fused to another flag, but it requires a value\n",
|
||||
.{arg},
|
||||
);
|
||||
return ParseError.FusedShortTagValueMissing;
|
||||
};
|
||||
|
||||
try self.applyParamValues(param, argit, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try self.error_message.writer(self.allocator).print(
|
||||
"Could not parse command line: unknown option \"-{c}\"\n",
|
||||
.{arg},
|
||||
);
|
||||
return ParseError.UnknownShortTagParameter;
|
||||
}
|
||||
|
||||
fn parseOrdinals(
|
||||
self: *@This(),
|
||||
arg: []const u8,
|
||||
arg: [:0]u8,
|
||||
argit: *ncmeta.SliceIterator([][:0]u8),
|
||||
) ParseError!?ParserInterface {
|
||||
comptime var arg_index: u32 = 0;
|
||||
@@ -402,7 +399,20 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
arg_index += 1;
|
||||
}
|
||||
|
||||
return self.subcommands.get(arg) orelse ParseError.ExtraValue;
|
||||
return self.subcommands.get(arg) orelse {
|
||||
const writer = self.error_message.writer(self.allocator);
|
||||
if (self.subcommands.count() > 0)
|
||||
try writer.print(
|
||||
"Could not parse command line: unknown subcommand \"{s}\"\n",
|
||||
.{arg},
|
||||
)
|
||||
else
|
||||
try writer.print(
|
||||
"Could not parse command line: unexpected extra argument \"{s}\"\n",
|
||||
.{arg},
|
||||
);
|
||||
return ParseError.ExtraValue;
|
||||
};
|
||||
}
|
||||
|
||||
fn pushIntermediateValue(
|
||||
@@ -417,7 +427,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
if (@field(self.intermediate, param.name) == null) {
|
||||
@field(self.intermediate, param.name) = gen.IntermediateType().init(self.allocator);
|
||||
}
|
||||
@field(self.intermediate, param.name).?.append(value) catch return ParseError.UnexpectedFailure;
|
||||
try @field(self.intermediate, param.name).?.append(value);
|
||||
} else if (comptime @TypeOf(param).G.nonscalar()) {
|
||||
if (@field(self.intermediate, param.name)) |list| list.deinit();
|
||||
@field(self.intermediate, param.name) = value;
|
||||
@@ -436,18 +446,54 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
.flag => try self.pushIntermediateValue(param, comptime param.flag_bias.string()),
|
||||
.count => @field(self.intermediate, param.name) += 1,
|
||||
.fixed => |count| switch (count) {
|
||||
0 => return ParseError.ExtraValue,
|
||||
1 => try self.pushIntermediateValue(param, argit.next() orelse return ParseError.MissingValue),
|
||||
0 => {
|
||||
const writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try writer.print(
|
||||
"Could not parse command line: {s} takes no value.\n",
|
||||
.{desc},
|
||||
);
|
||||
return ParseError.ExtraValue;
|
||||
},
|
||||
1 => try self.pushIntermediateValue(param, argit.next() orelse {
|
||||
const writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try writer.print(
|
||||
"Could not parse command line: {s} requires a value.\n",
|
||||
.{desc},
|
||||
);
|
||||
return ParseError.MissingValue;
|
||||
}),
|
||||
else => |total| {
|
||||
var list = std.ArrayList([]const u8).initCapacity(self.allocator, total) catch
|
||||
return ParseError.UnexpectedFailure;
|
||||
var list = try std.ArrayList([:0]const u8).initCapacity(self.allocator, total);
|
||||
|
||||
var consumed: u32 = 0;
|
||||
while (consumed < total) : (consumed += 1) {
|
||||
const next = argit.next() orelse return ParseError.MissingValue;
|
||||
list.append(next) catch return ParseError.UnexpectedFailure;
|
||||
const next = argit.next() orelse {
|
||||
const writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try writer.print(
|
||||
"Could not parse command line: {s} is missing one or more values (need {d}, got {d}).\n",
|
||||
.{ desc, total, consumed },
|
||||
);
|
||||
return ParseError.MissingValue;
|
||||
};
|
||||
|
||||
list.appendAssumeCapacity(next);
|
||||
}
|
||||
if (bounded and argit.next() != null) {
|
||||
const writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try writer.print(
|
||||
"Could not parse command line: {s} has too many values (need {d}).\n",
|
||||
.{ desc, total },
|
||||
);
|
||||
return ParseError.ExtraValue;
|
||||
}
|
||||
if (bounded and argit.next() != null) return ParseError.ExtraValue;
|
||||
|
||||
try self.pushIntermediateValue(param, list);
|
||||
},
|
||||
@@ -458,9 +504,9 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
fn applyFusedValues(
|
||||
self: *@This(),
|
||||
comptime param: anytype,
|
||||
value: []const u8,
|
||||
value: [:0]u8,
|
||||
) ParseError!void {
|
||||
var iter = std.mem.split(u8, value, ",");
|
||||
var iter = ncmeta.MutatingZSplitter(u8){ .buffer = value, .delimiter = ',' };
|
||||
return try self.applyParamValues(param, &iter, true);
|
||||
}
|
||||
|
||||
@@ -468,7 +514,9 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
inline for (comptime parameters) |param| {
|
||||
if (comptime param.env_var) |env_var| blk: {
|
||||
if (@field(self.intermediate, param.name) != null) break :blk;
|
||||
const val = env.get(env_var) orelse break :blk;
|
||||
|
||||
const val = try self.allocator.dupeZ(u8, env.get(env_var) orelse break :blk);
|
||||
|
||||
if (comptime @TypeOf(param).G.value_count == .flag) {
|
||||
try self.pushIntermediateValue(param, val);
|
||||
} else {
|
||||
@@ -501,19 +549,27 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
|
||||
if (comptime @TypeOf(param).has_output) {
|
||||
@field(self.output, param.name) = param.converter(context, intermediate, writer) catch |err| {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("Error parsing option \"{s}\": {s}\n", .{ param.name, buffer.items }) catch {};
|
||||
const err_writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try err_writer.print("Error parsing option {s}: {s}\n", .{ desc, buffer.items });
|
||||
return err;
|
||||
};
|
||||
} else {
|
||||
param.converter(context, intermediate, writer) catch |err| {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("Error parsing option \"{s}\": {s}\n", .{ param.name, buffer.items }) catch {};
|
||||
const err_writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try err_writer.print("Error parsing option {s}: {s}\n", .{ desc, buffer.items });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (comptime param.required) {
|
||||
const err_writer = self.error_message.writer(self.allocator);
|
||||
const desc = try param.describe(self.allocator);
|
||||
defer self.allocator.free(desc);
|
||||
try err_writer.print("Could not parse command line: required parameter {s} is missing\n", .{desc});
|
||||
return ParseError.RequiredParameterMissing;
|
||||
} else if (comptime @TypeOf(param).has_output) {
|
||||
if (comptime param.default) |def| {
|
||||
@@ -540,3 +596,98 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn InterfaceWrappers(comptime ParserType: type) type {
|
||||
return struct {
|
||||
inline fn castInterfaceParser(parser: *anyopaque) *ParserType {
|
||||
return @ptrCast(@alignCast(parser));
|
||||
}
|
||||
|
||||
fn _wrapExecute(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
|
||||
const self = castInterfaceParser(parser);
|
||||
|
||||
const context = self.castContext(ctx);
|
||||
return try self.execute(context);
|
||||
}
|
||||
|
||||
fn _wrapParse(
|
||||
parser: *anyopaque,
|
||||
ctx: *anyopaque,
|
||||
name: []const u8,
|
||||
args: [][:0]u8,
|
||||
env: std.process.EnvMap,
|
||||
) anyerror!?ParseResult {
|
||||
const self = castInterfaceParser(parser);
|
||||
const context = self.castContext(ctx);
|
||||
return try self.subparse(context, name, args, env);
|
||||
}
|
||||
|
||||
fn _wrapFinish(parser: *anyopaque, ctx: *anyopaque) anyerror!?ParserInterface {
|
||||
const self = castInterfaceParser(parser);
|
||||
const context = self.castContext(ctx);
|
||||
return try self.finish(context);
|
||||
}
|
||||
|
||||
fn _wrapGetParseError(parser: *anyopaque) []const u8 {
|
||||
const self = castInterfaceParser(parser);
|
||||
return self.getParseError();
|
||||
}
|
||||
|
||||
fn _wrapAddSubcommand(parser: *anyopaque, name: []const u8, subcommand: ParserInterface) !void {
|
||||
const self = castInterfaceParser(parser);
|
||||
return self.addSubcommand(name, subcommand);
|
||||
}
|
||||
|
||||
fn _wrapGetSubcommand(parser: *anyopaque, name: []const u8) ?ParserInterface {
|
||||
const self = castInterfaceParser(parser);
|
||||
return self.getSubcommand(name);
|
||||
}
|
||||
|
||||
fn _wrapDeinit(parser: *anyopaque) void {
|
||||
const self = castInterfaceParser(parser);
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
fn _wrapDeinitTree(parser: *anyopaque) void {
|
||||
const self = castInterfaceParser(parser);
|
||||
self.deinitTree();
|
||||
}
|
||||
|
||||
fn _wrapDescribe() []const u8 {
|
||||
return ParserType.command_description;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: figure out a better way of consolidating this logic with that in command.zig?
|
||||
fn InterfaceGen(comptime ParserType: type, comptime ICC: anytype) type {
|
||||
return switch (ICC) {
|
||||
.empty => struct {
|
||||
pub fn interface(self: *ParserType) ParserInterface {
|
||||
return ParserInterface.create(ParserType, self, @constCast(&void{}));
|
||||
}
|
||||
|
||||
fn castContext(_: ParserType, _: *anyopaque) void {
|
||||
return void{};
|
||||
}
|
||||
},
|
||||
.pointer => struct {
|
||||
pub fn interface(self: *ParserType, context: ICC.InputType().?) ParserInterface {
|
||||
return ParserInterface.create(ParserType, self, @constCast(context));
|
||||
}
|
||||
|
||||
fn castContext(_: ParserType, ctx: *anyopaque) ICC.OutputType() {
|
||||
return @ptrCast(@alignCast(ctx));
|
||||
}
|
||||
},
|
||||
.value => struct {
|
||||
pub fn interface(self: *ParserType, context: ICC.InputType().?) ParserInterface {
|
||||
return ParserInterface.create(ParserType, self, @ptrCast(@constCast(context)));
|
||||
}
|
||||
|
||||
fn castContext(_: ParserType, ctx: *anyopaque) ICC.OutputType() {
|
||||
return @as(ICC.InputType().?, @ptrCast(@alignCast(ctx))).*;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user