Compare commits
2 Commits
master
...
419d8994ba
Author | SHA1 | Date | |
---|---|---|---|
419d8994ba
|
|||
a8652f71c4
|
12
build.zig
12
build.zig
@@ -1,11 +1,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const target: std.Build.ResolvedTarget = b.standardTargetOptions(.{});
|
const target: std.zig.CrossTarget = b.standardTargetOptions(.{});
|
||||||
const optimize: std.builtin.Mode = b.standardOptimizeOption(.{});
|
const optimize: std.builtin.Mode = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const noclip = b.addModule("noclip", .{
|
const noclip = b.addModule("noclip", .{
|
||||||
.root_source_file = b.path("source/noclip.zig"),
|
.source_file = .{ .path = "source/noclip.zig" },
|
||||||
});
|
});
|
||||||
|
|
||||||
demo(b, noclip, target, optimize);
|
demo(b, noclip, target, optimize);
|
||||||
@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
const tests = b.addTest(.{
|
const tests = b.addTest(.{
|
||||||
.name = "tests",
|
.name = "tests",
|
||||||
.root_source_file = b.path("source/noclip.zig"),
|
.root_source_file = .{ .path = "source/noclip.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
@@ -24,18 +24,18 @@ pub fn build(b: *std.Build) void {
|
|||||||
fn demo(
|
fn demo(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
noclip: *std.Build.Module,
|
noclip: *std.Build.Module,
|
||||||
target: std.Build.ResolvedTarget,
|
target: std.zig.CrossTarget,
|
||||||
optimize: std.builtin.Mode,
|
optimize: std.builtin.Mode,
|
||||||
) void {
|
) void {
|
||||||
const demo_step = b.step("demo", "Build and install CLI demo program");
|
const demo_step = b.step("demo", "Build and install CLI demo program");
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "noclip-demo",
|
.name = "noclip-demo",
|
||||||
.root_source_file = b.path("demo/demo.zig"),
|
.root_source_file = .{ .path = "demo/demo.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
exe.root_module.addImport("noclip", noclip);
|
exe.addModule("noclip", noclip);
|
||||||
const install_demo = b.addInstallArtifact(exe, .{});
|
const install_demo = b.addInstallArtifact(exe, .{});
|
||||||
|
|
||||||
demo_step.dependOn(&install_demo.step);
|
demo_step.dependOn(&install_demo.step);
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
.{
|
|
||||||
.name = .NOCLIP,
|
|
||||||
.fingerprint = 0xE4C223E8CB9C8ADF,
|
|
||||||
.version = "0.1.0-pre",
|
|
||||||
.minimum_zig_version = "0.14.0",
|
|
||||||
.dependencies = .{},
|
|
||||||
.paths = .{
|
|
||||||
"source",
|
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
"license",
|
|
||||||
},
|
|
||||||
}
|
|
@@ -8,22 +8,9 @@ const Choice = enum { first, second };
|
|||||||
const cli = cmd: {
|
const cli = cmd: {
|
||||||
var cmd = CommandBuilder(*u32){
|
var cmd = CommandBuilder(*u32){
|
||||||
.description =
|
.description =
|
||||||
\\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!
|
||||||
\\
|
|
||||||
\\> // 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 } }, .{
|
cmd.addOption(.{ .OutputType = struct { u8, u8 } }, .{
|
||||||
@@ -135,20 +122,7 @@ pub fn main() !u8 {
|
|||||||
try base.addSubcommand("main", try cli.createInterface(allocator, cliHandler, &context));
|
try base.addSubcommand("main", try cli.createInterface(allocator, cliHandler, &context));
|
||||||
try base.addSubcommand("other", try subcommand.createInterface(allocator, subHandler, &sc));
|
try base.addSubcommand("other", try subcommand.createInterface(allocator, subHandler, &sc));
|
||||||
|
|
||||||
const group = try noclip.commandGroup(allocator, .{ .description = "final level of a deeply nested subcommand" });
|
try base.execute();
|
||||||
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ ____
|
|||||||
|
|
||||||
== Hello
|
== Hello
|
||||||
|
|
||||||
Requires Zig `0.13.x`. May work with `0.12.x`.
|
Requires Zig `0.11.x`.
|
||||||
|
|
||||||
=== Features
|
=== Features
|
||||||
|
|
||||||
|
@@ -118,8 +118,8 @@ pub const InterfaceContextCategory = union(enum) {
|
|||||||
|
|
||||||
pub fn fromType(comptime ContextType: type) InterfaceContextCategory {
|
pub fn fromType(comptime ContextType: type) InterfaceContextCategory {
|
||||||
return switch (@typeInfo(ContextType)) {
|
return switch (@typeInfo(ContextType)) {
|
||||||
.void => .empty,
|
.Void => .empty,
|
||||||
.pointer => |info| if (info.size == .slice) .{ .value = ContextType } else .{ .pointer = ContextType },
|
.Pointer => |info| if (info.size == .Slice) .{ .value = ContextType } else .{ .pointer = ContextType },
|
||||||
// technically, i0, u0, and struct{} should be treated as empty, probably
|
// technically, i0, u0, and struct{} should be treated as empty, probably
|
||||||
else => .{ .value = ContextType },
|
else => .{ .value = ContextType },
|
||||||
};
|
};
|
||||||
@@ -167,13 +167,12 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
|||||||
return Parser(self, callback){
|
return Parser(self, callback){
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.allocator = arena_alloc,
|
.allocator = arena_alloc,
|
||||||
.subcommands = parser.CommandMap.init(arena_alloc),
|
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(arena_alloc),
|
||||||
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ifc = InterfaceCreator(@This());
|
pub usingnamespace InterfaceCreator(@This());
|
||||||
pub const createInterface = ifc.createInterface;
|
|
||||||
|
|
||||||
fn _createInterfaceImpl(
|
fn _createInterfaceImpl(
|
||||||
comptime self: @This(),
|
comptime self: @This(),
|
||||||
@@ -189,7 +188,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
|||||||
this_parser.* = .{
|
this_parser.* = .{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.allocator = arena_alloc,
|
.allocator = arena_alloc,
|
||||||
.subcommands = parser.CommandMap.init(arena_alloc),
|
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(arena_alloc),
|
||||||
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -373,29 +372,27 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
|||||||
// global tags and env_vars would conflict, which is less common.
|
// global tags and env_vars would conflict, which is less common.
|
||||||
if (param.short_tag) |short|
|
if (param.short_tag) |short|
|
||||||
tag_fields = tag_fields ++ &[_]StructField{.{
|
tag_fields = tag_fields ++ &[_]StructField{.{
|
||||||
// this goofy construct coerces the comptime []const u8 to
|
.name = short,
|
||||||
// [:0]const u8.
|
|
||||||
.name = short ++ "",
|
|
||||||
.type = void,
|
.type = void,
|
||||||
.default_value_ptr = null,
|
.default_value = null,
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = 0,
|
.alignment = 0,
|
||||||
}};
|
}};
|
||||||
|
|
||||||
if (param.long_tag) |long|
|
if (param.long_tag) |long|
|
||||||
tag_fields = tag_fields ++ &[_]StructField{.{
|
tag_fields = tag_fields ++ &[_]StructField{.{
|
||||||
.name = long ++ "",
|
.name = long,
|
||||||
.type = void,
|
.type = void,
|
||||||
.default_value_ptr = null,
|
.default_value = null,
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = 0,
|
.alignment = 0,
|
||||||
}};
|
}};
|
||||||
|
|
||||||
if (param.env_var) |env_var|
|
if (param.env_var) |env_var|
|
||||||
env_var_fields = env_var_fields ++ &[_]StructField{.{
|
env_var_fields = env_var_fields ++ &[_]StructField{.{
|
||||||
.name = env_var ++ "",
|
.name = env_var,
|
||||||
.type = void,
|
.type = void,
|
||||||
.default_value_ptr = null,
|
.default_value = null,
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = 0,
|
.alignment = 0,
|
||||||
}};
|
}};
|
||||||
@@ -438,30 +435,30 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
|||||||
const default = if (param.default) |def| &@as(FieldType, def) else @as(?*const anyopaque, null);
|
const default = if (param.default) |def| &@as(FieldType, def) else @as(?*const anyopaque, null);
|
||||||
|
|
||||||
fields = fields ++ &[_]StructField{.{
|
fields = fields ++ &[_]StructField{.{
|
||||||
.name = param.name ++ "",
|
.name = param.name,
|
||||||
.type = FieldType,
|
.type = FieldType,
|
||||||
.default_value_ptr = @ptrCast(default),
|
.default_value = @ptrCast(default),
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = @alignOf(FieldType),
|
.alignment = @alignOf(FieldType),
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = @Type(.{ .@"struct" = .{
|
_ = @Type(.{ .Struct = .{
|
||||||
.layout = .auto,
|
.layout = .Auto,
|
||||||
.fields = tag_fields,
|
.fields = tag_fields,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
.is_tuple = false,
|
.is_tuple = false,
|
||||||
} });
|
} });
|
||||||
|
|
||||||
_ = @Type(.{ .@"struct" = .{
|
_ = @Type(.{ .Struct = .{
|
||||||
.layout = .auto,
|
.layout = .Auto,
|
||||||
.fields = env_var_fields,
|
.fields = env_var_fields,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
.is_tuple = false,
|
.is_tuple = false,
|
||||||
} });
|
} });
|
||||||
|
|
||||||
return @Type(.{ .@"struct" = .{
|
return @Type(.{ .Struct = .{
|
||||||
.layout = .auto,
|
.layout = .Auto,
|
||||||
.fields = fields,
|
.fields = fields,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
.is_tuple = false,
|
.is_tuple = false,
|
||||||
@@ -508,9 +505,9 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
|||||||
?PType.G.IntermediateType();
|
?PType.G.IntermediateType();
|
||||||
|
|
||||||
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
|
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
|
||||||
.name = param.name ++ "",
|
.name = param.name,
|
||||||
.type = FieldType,
|
.type = FieldType,
|
||||||
.default_value_ptr = @ptrCast(&@as(
|
.default_value = @ptrCast(&@as(
|
||||||
FieldType,
|
FieldType,
|
||||||
if (PType.value_count == .count) 0 else null,
|
if (PType.value_count == .count) 0 else null,
|
||||||
)),
|
)),
|
||||||
@@ -519,8 +516,8 @@ pub fn CommandBuilder(comptime UserContext: type) type {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
return @Type(.{ .@"struct" = .{
|
return @Type(.{ .Struct = .{
|
||||||
.layout = .auto,
|
.layout = .Auto,
|
||||||
.fields = fields,
|
.fields = fields,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
.is_tuple = false,
|
.is_tuple = false,
|
||||||
|
@@ -21,16 +21,16 @@ pub fn DefaultConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen
|
|||||||
return if (comptime gen.multi)
|
return if (comptime gen.multi)
|
||||||
MultiConverter(gen)
|
MultiConverter(gen)
|
||||||
else switch (@typeInfo(gen.OutputType)) {
|
else switch (@typeInfo(gen.OutputType)) {
|
||||||
.bool => FlagConverter(gen),
|
.Bool => FlagConverter(gen),
|
||||||
.int => IntConverter(gen),
|
.Int => IntConverter(gen),
|
||||||
.pointer => |info| if (info.size == .slice and info.child == u8)
|
.Pointer => |info| if (info.size == .Slice and info.child == u8)
|
||||||
StringConverter(gen)
|
StringConverter(gen)
|
||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
.@"enum" => |info| if (info.is_exhaustive) ChoiceConverter(gen) else null,
|
.Enum => |info| if (info.is_exhaustive) ChoiceConverter(gen) else null,
|
||||||
// TODO: how to handle structs with field defaults? maybe this should only work
|
// TODO: how to handle structs with field defaults? maybe this should only work
|
||||||
// for tuples, which I don't think can have defaults.
|
// for tuples, which I don't think can have defaults.
|
||||||
.@"struct" => |info| if (gen.value_count == .fixed and gen.value_count.fixed == info.fields.len)
|
.Struct => |info| if (gen.value_count == .fixed and gen.value_count.fixed == info.fields.len)
|
||||||
StructConverter(gen)
|
StructConverter(gen)
|
||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
@@ -102,7 +102,7 @@ fn IntConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|||||||
|
|
||||||
fn StructConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
fn StructConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
||||||
const StructType = gen.OutputType;
|
const StructType = gen.OutputType;
|
||||||
const type_info = @typeInfo(StructType).@"struct";
|
const type_info = @typeInfo(StructType).Struct;
|
||||||
const Intermediate = gen.IntermediateType();
|
const Intermediate = gen.IntermediateType();
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
@@ -120,7 +120,7 @@ fn StructConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|||||||
const Converter = comptime DefaultConverter(
|
const Converter = comptime DefaultConverter(
|
||||||
ncmeta.copyStruct(ParameterGenerics, gen, .{
|
ncmeta.copyStruct(ParameterGenerics, gen, .{
|
||||||
.OutputType = field.type,
|
.OutputType = field.type,
|
||||||
.value_count = @as(parameters.ValueCount, .{ .fixed = 1 }),
|
.value_count = .{ .fixed = 1 },
|
||||||
}),
|
}),
|
||||||
) orelse
|
) orelse
|
||||||
@compileError("cannot get converter for field" ++ field.name);
|
@compileError("cannot get converter for field" ++ field.name);
|
||||||
|
@@ -12,7 +12,6 @@ pub const ParseError = error{
|
|||||||
UnknownLongTagParameter,
|
UnknownLongTagParameter,
|
||||||
UnknownShortTagParameter,
|
UnknownShortTagParameter,
|
||||||
RequiredParameterMissing,
|
RequiredParameterMissing,
|
||||||
OutOfMemory,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NoclipError = ParseError || ConversionError;
|
pub const NoclipError = ParseError || ConversionError;
|
||||||
|
@@ -57,7 +57,7 @@ pub fn StructuredPrinter(comptime Writer: type) type {
|
|||||||
|
|
||||||
// this assumes output stream has already had the first line properly
|
// this assumes output stream has already had the first line properly
|
||||||
// indented.
|
// indented.
|
||||||
var splitter = std.mem.splitScalar(u8, text, '\n');
|
var splitter = std.mem.split(u8, text, "\n");
|
||||||
|
|
||||||
var location: usize = indent;
|
var location: usize = indent;
|
||||||
while (splitter.next()) |line| {
|
while (splitter.next()) |line| {
|
||||||
@@ -65,17 +65,6 @@ pub fn StructuredPrinter(comptime Writer: type) type {
|
|||||||
// we have a trailing line that needs to be cleaned up
|
// we have a trailing line that needs to be cleaned up
|
||||||
if (location > indent)
|
if (location > indent)
|
||||||
_ = try self.clearLine(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);
|
location = try self.clearLine(indent);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -112,7 +101,6 @@ pub fn StructuredPrinter(comptime Writer: type) type {
|
|||||||
}
|
}
|
||||||
if (location > indent)
|
if (location > indent)
|
||||||
try self.writer.writeByte(' ');
|
try self.writer.writeByte(' ');
|
||||||
|
|
||||||
try self.writer.writeAll(choppee[0..split]);
|
try self.writer.writeAll(choppee[0..split]);
|
||||||
location = try self.clearLine(indent);
|
location = try self.clearLine(indent);
|
||||||
choppee = choppee[split + 1 ..];
|
choppee = choppee[split + 1 ..];
|
||||||
@@ -360,10 +348,11 @@ pub fn HelpBuilder(comptime command: anytype) type {
|
|||||||
defer pairs.deinit();
|
defer pairs.deinit();
|
||||||
|
|
||||||
var just: usize = 0;
|
var just: usize = 0;
|
||||||
for (subcommands.keys()) |key| {
|
var iter = subcommands.keyIterator();
|
||||||
|
while (iter.next()) |key| {
|
||||||
const pair: AlignablePair = .{
|
const pair: AlignablePair = .{
|
||||||
.left = key,
|
.left = key.*,
|
||||||
.right = subcommands.get(key).?.describe(),
|
.right = subcommands.get(key.*).?.describe(),
|
||||||
};
|
};
|
||||||
if (pair.left.len > just) just = pair.left.len;
|
if (pair.left.len > just) just = pair.left.len;
|
||||||
try pairs.append(pair);
|
try pairs.append(pair);
|
||||||
@@ -484,7 +473,7 @@ pub fn optInfo(comptime command: anytype) CommandHelp {
|
|||||||
// than just the tag name. Roll our own eventually.
|
// than just the tag name. Roll our own eventually.
|
||||||
blk: {
|
blk: {
|
||||||
switch (@typeInfo(@TypeOf(def))) {
|
switch (@typeInfo(@TypeOf(def))) {
|
||||||
.pointer => |info| if (info.size == .Slice and info.child == u8) {
|
.Pointer => |info| if (info.size == .Slice and info.child == u8) {
|
||||||
writer.print("{s}", .{def}) catch @compileError("no");
|
writer.print("{s}", .{def}) catch @compileError("no");
|
||||||
break :blk;
|
break :blk;
|
||||||
},
|
},
|
||||||
|
@@ -10,7 +10,7 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
|
|||||||
comptime {
|
comptime {
|
||||||
const inputInfo = @typeInfo(input);
|
const inputInfo = @typeInfo(input);
|
||||||
const fieldcount = switch (inputInfo) {
|
const fieldcount = switch (inputInfo) {
|
||||||
.@"struct" => |spec| blk: {
|
.Struct => |spec| blk: {
|
||||||
if (spec.decls.len > 0) {
|
if (spec.decls.len > 0) {
|
||||||
@compileError("UpdateDefaults only works on structs " ++
|
@compileError("UpdateDefaults only works on structs " ++
|
||||||
"without decls due to limitations in @Type.");
|
"without decls due to limitations in @Type.");
|
||||||
@@ -21,7 +21,7 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var fields: [fieldcount]StructField = undefined;
|
var fields: [fieldcount]StructField = undefined;
|
||||||
for (inputInfo.@"struct".fields, 0..) |field, idx| {
|
for (inputInfo.Struct.fields, 0..) |field, idx| {
|
||||||
fields[idx] = .{
|
fields[idx] = .{
|
||||||
.name = field.name,
|
.name = field.name,
|
||||||
.field_type = field.field_type,
|
.field_type = field.field_type,
|
||||||
@@ -29,27 +29,27 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
|
|||||||
// setting null defaults work, and it converts comptime_int to
|
// setting null defaults work, and it converts comptime_int to
|
||||||
// the appropriate type, which is nice for ergonomics. Not sure
|
// the appropriate type, which is nice for ergonomics. Not sure
|
||||||
// if it introduces weird edge cases. Probably it's fine?
|
// if it introduces weird edge cases. Probably it's fine?
|
||||||
.default_value_ptr = if (@hasField(@TypeOf(defaults), field.name))
|
.default_value = if (@hasField(@TypeOf(defaults), field.name))
|
||||||
@ptrCast(&@as(field.field_type, @field(defaults, field.name)))
|
@ptrCast(&@as(field.field_type, @field(defaults, field.name)))
|
||||||
else
|
else
|
||||||
field.default_value_ptr,
|
field.default_value,
|
||||||
.is_comptime = field.is_comptime,
|
.is_comptime = field.is_comptime,
|
||||||
.alignment = field.alignment,
|
.alignment = field.alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return @Type(.{ .@"struct" = .{
|
return @Type(.{ .Struct = .{
|
||||||
.layout = inputInfo.@"struct".layout,
|
.layout = inputInfo.Struct.layout,
|
||||||
.backing_integer = inputInfo.@"struct".backing_integer,
|
.backing_integer = inputInfo.Struct.backing_integer,
|
||||||
.fields = &fields,
|
.fields = &fields,
|
||||||
.decls = inputInfo.@"struct".decls,
|
.decls = inputInfo.Struct.decls,
|
||||||
.is_tuple = inputInfo.@"struct".is_tuple,
|
.is_tuple = inputInfo.Struct.is_tuple,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumLength(comptime T: type) comptime_int {
|
pub fn enumLength(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 []const T) [3][]const T {
|
pub fn partition(comptime T: type, input: []const T, wedge: []const []const T) [3][]const T {
|
||||||
@@ -210,11 +210,11 @@ pub fn MutatingZSplitter(comptime T: type) type {
|
|||||||
pub fn copyStruct(comptime T: type, source: T, field_overrides: anytype) T {
|
pub fn copyStruct(comptime T: type, source: T, field_overrides: anytype) T {
|
||||||
var result: T = undefined;
|
var result: T = undefined;
|
||||||
|
|
||||||
comptime for (@typeInfo(@TypeOf(field_overrides)).@"struct".fields) |field| {
|
comptime inline for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
|
||||||
if (!@hasField(T, field.name)) @compileError("override contains bad field" ++ field);
|
if (!@hasField(T, field.name)) @compileError("override contains bad field" ++ field);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (comptime @typeInfo(T).@"struct".fields) |field| {
|
inline for (comptime @typeInfo(T).Struct.fields) |field| {
|
||||||
if (comptime @hasField(@TypeOf(field_overrides), field.name))
|
if (comptime @hasField(@TypeOf(field_overrides), field.name))
|
||||||
@field(result, field.name) = @field(field_overrides, field.name)
|
@field(result, field.name) = @field(field_overrides, field.name)
|
||||||
else
|
else
|
||||||
@@ -254,18 +254,19 @@ pub const TupleBuilder = struct {
|
|||||||
comptime {
|
comptime {
|
||||||
var fields: [self.types.len]StructField = undefined;
|
var fields: [self.types.len]StructField = undefined;
|
||||||
for (self.types, 0..) |Type, idx| {
|
for (self.types, 0..) |Type, idx| {
|
||||||
|
var num_buf: [128]u8 = undefined;
|
||||||
fields[idx] = .{
|
fields[idx] = .{
|
||||||
.name = std.fmt.comptimePrint("{d}", .{idx}),
|
.name = std.fmt.bufPrint(&num_buf, "{d}", .{idx}) catch @compileError("failed to write field"),
|
||||||
.type = Type,
|
.type = Type,
|
||||||
.default_value_ptr = null,
|
.default_value = null,
|
||||||
// TODO: is this the right thing to do?
|
// TODO: is this the right thing to do?
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = if (@sizeOf(Type) > 0) @alignOf(Type) else 0,
|
.alignment = if (@sizeOf(Type) > 0) @alignOf(Type) else 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return @Type(.{ .@"struct" = .{
|
return @Type(.{ .Struct = .{
|
||||||
.layout = .auto,
|
.layout = .Auto,
|
||||||
.fields = &fields,
|
.fields = &fields,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
.is_tuple = true,
|
.is_tuple = true,
|
||||||
|
@@ -7,5 +7,4 @@ pub const parameters = @import("./parameters.zig");
|
|||||||
pub const parser = @import("./parser.zig");
|
pub const parser = @import("./parser.zig");
|
||||||
|
|
||||||
pub const CommandBuilder = command.CommandBuilder;
|
pub const CommandBuilder = command.CommandBuilder;
|
||||||
pub const commandGroup = command.commandGroup;
|
|
||||||
pub const ParserInterface = parser.ParserInterface;
|
pub const ParserInterface = parser.ParserInterface;
|
||||||
|
@@ -48,11 +48,11 @@ pub const ParameterGenerics = struct {
|
|||||||
pub fn fixedValueCount(comptime OutputType: type, comptime value_count: ValueCount) ValueCount {
|
pub fn fixedValueCount(comptime OutputType: type, comptime value_count: ValueCount) ValueCount {
|
||||||
return comptime if (value_count == .fixed)
|
return comptime if (value_count == .fixed)
|
||||||
switch (@typeInfo(OutputType)) {
|
switch (@typeInfo(OutputType)) {
|
||||||
.@"struct" => |info| .{ .fixed = info.fields.len },
|
.Struct => |info| .{ .fixed = info.fields.len },
|
||||||
.array => |info| .{ .fixed = info.len },
|
.Array => |info| .{ .fixed = info.len },
|
||||||
// TODO: this is a bit sloppy, but it can be refined later.
|
// TODO: this is a bit sloppy, but it can be refined later.
|
||||||
// .Pointer covers slices, which may be a many-to-many conversion.
|
// .Pointer covers slices, which may be a many-to-many conversion.
|
||||||
.pointer => value_count,
|
.Pointer => value_count,
|
||||||
else => .{ .fixed = 1 },
|
else => .{ .fixed = 1 },
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -223,26 +223,6 @@ fn OptionType(comptime generics: ParameterGenerics) type {
|
|||||||
/// want weird things to happen.
|
/// want weird things to happen.
|
||||||
flag_bias: FlagBias,
|
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 {
|
pub fn IntermediateValue(comptime _: @This()) type {
|
||||||
return generics.IntermediateValue();
|
return generics.IntermediateValue();
|
||||||
}
|
}
|
||||||
|
@@ -10,9 +10,8 @@ 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, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!?ParseResult,
|
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!?ParserInterface,
|
finish: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
|
||||||
getParseError: *const fn (parser: *anyopaque) []const u8,
|
|
||||||
addSubcommand: *const fn (parser: *anyopaque, name: []const u8, subcommand: ParserInterface) std.mem.Allocator.Error!void,
|
addSubcommand: *const fn (parser: *anyopaque, name: []const u8, subcommand: ParserInterface) std.mem.Allocator.Error!void,
|
||||||
getSubcommand: *const fn (parser: *anyopaque, name: []const u8) ?ParserInterface,
|
getSubcommand: *const fn (parser: *anyopaque, name: []const u8) ?ParserInterface,
|
||||||
describe: *const fn () []const u8,
|
describe: *const fn () []const u8,
|
||||||
@@ -32,7 +31,6 @@ pub const ParserInterface = struct {
|
|||||||
.execute = ParserType._wrapExecute,
|
.execute = ParserType._wrapExecute,
|
||||||
.parse = ParserType._wrapParse,
|
.parse = ParserType._wrapParse,
|
||||||
.finish = ParserType._wrapFinish,
|
.finish = ParserType._wrapFinish,
|
||||||
.getParseError = ParserType._wrapGetParseError,
|
|
||||||
.addSubcommand = ParserType._wrapAddSubcommand,
|
.addSubcommand = ParserType._wrapAddSubcommand,
|
||||||
.getSubcommand = ParserType._wrapGetSubcommand,
|
.getSubcommand = ParserType._wrapGetSubcommand,
|
||||||
.describe = ParserType._wrapDescribe,
|
.describe = ParserType._wrapDescribe,
|
||||||
@@ -46,18 +44,14 @@ 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(), name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!?ParseResult {
|
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);
|
return try self.methods.parse(self.parser, self.context, name, args, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self: @This()) anyerror!?ParserInterface {
|
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 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 {
|
pub fn addSubcommand(self: @This(), name: []const u8, subcommand: ParserInterface) std.mem.Allocator.Error!void {
|
||||||
return try self.methods.addSubcommand(self.parser, name, subcommand);
|
return try self.methods.addSubcommand(self.parser, name, subcommand);
|
||||||
}
|
}
|
||||||
@@ -79,8 +73,7 @@ pub const ParserInterface = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CommandMap = std.StringArrayHashMap(ParserInterface);
|
pub const CommandMap = std.hash_map.StringHashMap(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
|
// 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.
|
||||||
@@ -102,7 +95,6 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
subcommands: CommandMap,
|
subcommands: CommandMap,
|
||||||
subcommand: ?ParserInterface = null,
|
subcommand: ?ParserInterface = null,
|
||||||
error_message: std.ArrayListUnmanaged(u8) = .{},
|
|
||||||
help_builder: help.HelpBuilder(command),
|
help_builder: help.HelpBuilder(command),
|
||||||
|
|
||||||
// This is a slightly annoying hack to work around the fact that there's no way
|
// This is a slightly annoying hack to work around the fact that there's no way
|
||||||
@@ -114,59 +106,13 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
// cognitively clutter this struct.
|
// cognitively clutter this struct.
|
||||||
pub usingnamespace InterfaceWrappers(@This());
|
pub usingnamespace InterfaceWrappers(@This());
|
||||||
|
|
||||||
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(
|
pub fn subparse(
|
||||||
self: *@This(),
|
self: *@This(),
|
||||||
context: UserContext,
|
context: UserContext,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
args: [][:0]u8,
|
args: [][:0]u8,
|
||||||
env: std.process.EnvMap,
|
env: std.process.EnvMap,
|
||||||
) anyerror!?ParseResult {
|
) anyerror!void {
|
||||||
const sliceto = try self.parse(name, args);
|
const sliceto = try self.parse(name, args);
|
||||||
try self.readEnvironment(env);
|
try self.readEnvironment(env);
|
||||||
try self.convertEager(context);
|
try self.convertEager(context);
|
||||||
@@ -175,30 +121,21 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
const grafted_name = try std.mem.join(
|
const grafted_name = try std.mem.join(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
" ",
|
" ",
|
||||||
&.{ name, args[sliceto - 1] },
|
&[_][]const u8{ name, args[sliceto - 1] },
|
||||||
);
|
);
|
||||||
return .{ .name = grafted_name, .args = args[sliceto..], .parser = subcommand };
|
try subcommand.parse(grafted_name, args[sliceto..], env);
|
||||||
} else if (self.subcommands.count() > 0 and command.subcommand_required) {
|
} else if (self.subcommands.count() > 0 and command.subcommand_required) {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
try stderr.print("'{s}' requires a subcommand.\n\n", .{name});
|
try stderr.writeAll("A subcommand is required.\n\n");
|
||||||
|
|
||||||
self.printHelp(name);
|
self.printHelp(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self: *@This(), context: UserContext) anyerror!?ParserInterface {
|
pub fn finish(self: *@This(), context: UserContext) anyerror!void {
|
||||||
try self.convert(context);
|
try self.convert(context);
|
||||||
try callback(context, self.output);
|
try callback(context, self.output);
|
||||||
return self.subcommand;
|
if (self.subcommand) |subcommand| try subcommand.finish();
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
pub fn deinit(self: @This()) void {
|
||||||
@@ -207,7 +144,8 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinitTree(self: @This()) void {
|
pub fn deinitTree(self: @This()) void {
|
||||||
for (self.subcommands.values()) |subcommand| {
|
var iterator = self.subcommands.valueIterator();
|
||||||
|
while (iterator.next()) |subcommand| {
|
||||||
subcommand.deinitTree();
|
subcommand.deinitTree();
|
||||||
}
|
}
|
||||||
self.deinit();
|
self.deinit();
|
||||||
@@ -221,6 +159,18 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
return self.subcommands.get(name);
|
return self.subcommands.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
fn printValue(self: @This(), value: anytype, comptime indent: []const u8) void {
|
fn printValue(self: @This(), value: anytype, comptime indent: []const u8) void {
|
||||||
if (comptime @hasField(@TypeOf(value), "items")) {
|
if (comptime @hasField(@TypeOf(value), "items")) {
|
||||||
std.debug.print("{s}[\n", .{indent});
|
std.debug.print("{s}[\n", .{indent});
|
||||||
@@ -330,10 +280,6 @@ 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;
|
return ParseError.UnknownLongTagParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,23 +302,14 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
|
|
||||||
if (arg == tag[1]) {
|
if (arg == tag[1]) {
|
||||||
if (comptime !PType.is_flag)
|
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;
|
return ParseError.FusedShortTagValueMissing;
|
||||||
};
|
|
||||||
|
|
||||||
try self.applyParamValues(param, argit, false);
|
try self.applyParamValues(param, argit, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.error_message.writer(self.allocator).print(
|
|
||||||
"Could not parse command line: unknown option \"-{c}\"\n",
|
|
||||||
.{arg},
|
|
||||||
);
|
|
||||||
return ParseError.UnknownShortTagParameter;
|
return ParseError.UnknownShortTagParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,20 +336,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
arg_index += 1;
|
arg_index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.subcommands.get(arg) orelse {
|
return self.subcommands.get(arg) orelse ParseError.ExtraValue;
|
||||||
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(
|
fn pushIntermediateValue(
|
||||||
@@ -427,7 +351,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
if (@field(self.intermediate, param.name) == null) {
|
if (@field(self.intermediate, param.name) == null) {
|
||||||
@field(self.intermediate, param.name) = gen.IntermediateType().init(self.allocator);
|
@field(self.intermediate, param.name) = gen.IntermediateType().init(self.allocator);
|
||||||
}
|
}
|
||||||
try @field(self.intermediate, param.name).?.append(value);
|
@field(self.intermediate, param.name).?.append(value) catch return ParseError.UnexpectedFailure;
|
||||||
} else if (comptime @TypeOf(param).G.nonscalar()) {
|
} else if (comptime @TypeOf(param).G.nonscalar()) {
|
||||||
if (@field(self.intermediate, param.name)) |list| list.deinit();
|
if (@field(self.intermediate, param.name)) |list| list.deinit();
|
||||||
@field(self.intermediate, param.name) = value;
|
@field(self.intermediate, param.name) = value;
|
||||||
@@ -446,54 +370,19 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
.flag => try self.pushIntermediateValue(param, comptime param.flag_bias.string()),
|
.flag => try self.pushIntermediateValue(param, comptime param.flag_bias.string()),
|
||||||
.count => @field(self.intermediate, param.name) += 1,
|
.count => @field(self.intermediate, param.name) += 1,
|
||||||
.fixed => |count| switch (count) {
|
.fixed => |count| switch (count) {
|
||||||
0 => {
|
0 => return ParseError.ExtraValue,
|
||||||
const writer = self.error_message.writer(self.allocator);
|
1 => try self.pushIntermediateValue(param, argit.next() orelse return ParseError.MissingValue),
|
||||||
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| {
|
else => |total| {
|
||||||
var list = try std.ArrayList([:0]const u8).initCapacity(self.allocator, total);
|
var list = std.ArrayList([:0]const u8).initCapacity(self.allocator, total) catch
|
||||||
|
return ParseError.UnexpectedFailure;
|
||||||
|
|
||||||
var consumed: u32 = 0;
|
var consumed: u32 = 0;
|
||||||
while (consumed < total) : (consumed += 1) {
|
while (consumed < total) : (consumed += 1) {
|
||||||
const next = argit.next() orelse {
|
const next = argit.next() orelse return ParseError.MissingValue;
|
||||||
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);
|
list.append(next) catch return ParseError.UnexpectedFailure;
|
||||||
}
|
|
||||||
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);
|
try self.pushIntermediateValue(param, list);
|
||||||
},
|
},
|
||||||
@@ -515,7 +404,8 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
if (comptime param.env_var) |env_var| blk: {
|
if (comptime param.env_var) |env_var| blk: {
|
||||||
if (@field(self.intermediate, param.name) != null) break :blk;
|
if (@field(self.intermediate, param.name) != null) break :blk;
|
||||||
|
|
||||||
const val = try self.allocator.dupeZ(u8, env.get(env_var) orelse break :blk);
|
const val = self.allocator.dupeZ(u8, env.get(env_var) orelse break :blk) catch
|
||||||
|
return ParseError.UnexpectedFailure;
|
||||||
|
|
||||||
if (comptime @TypeOf(param).G.value_count == .flag) {
|
if (comptime @TypeOf(param).G.value_count == .flag) {
|
||||||
try self.pushIntermediateValue(param, val);
|
try self.pushIntermediateValue(param, val);
|
||||||
@@ -549,27 +439,19 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
|||||||
|
|
||||||
if (comptime @TypeOf(param).has_output) {
|
if (comptime @TypeOf(param).has_output) {
|
||||||
@field(self.output, param.name) = param.converter(context, intermediate, writer) catch |err| {
|
@field(self.output, param.name) = param.converter(context, intermediate, writer) catch |err| {
|
||||||
const err_writer = self.error_message.writer(self.allocator);
|
const stderr = std.io.getStdErr().writer();
|
||||||
const desc = try param.describe(self.allocator);
|
stderr.print("Error parsing option \"{s}\": {s}\n", .{ param.name, buffer.items }) catch {};
|
||||||
defer self.allocator.free(desc);
|
|
||||||
try err_writer.print("Error parsing option {s}: {s}\n", .{ desc, buffer.items });
|
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
param.converter(context, intermediate, writer) catch |err| {
|
param.converter(context, intermediate, writer) catch |err| {
|
||||||
const err_writer = self.error_message.writer(self.allocator);
|
const stderr = std.io.getStdErr().writer();
|
||||||
const desc = try param.describe(self.allocator);
|
stderr.print("Error parsing option \"{s}\": {s}\n", .{ param.name, buffer.items }) catch {};
|
||||||
defer self.allocator.free(desc);
|
|
||||||
try err_writer.print("Error parsing option {s}: {s}\n", .{ desc, buffer.items });
|
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (comptime param.required) {
|
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;
|
return ParseError.RequiredParameterMissing;
|
||||||
} else if (comptime @TypeOf(param).has_output) {
|
} else if (comptime @TypeOf(param).has_output) {
|
||||||
if (comptime param.default) |def| {
|
if (comptime param.default) |def| {
|
||||||
@@ -616,23 +498,18 @@ fn InterfaceWrappers(comptime ParserType: type) type {
|
|||||||
name: []const u8,
|
name: []const u8,
|
||||||
args: [][:0]u8,
|
args: [][:0]u8,
|
||||||
env: std.process.EnvMap,
|
env: std.process.EnvMap,
|
||||||
) anyerror!?ParseResult {
|
) anyerror!void {
|
||||||
const self = castInterfaceParser(parser);
|
const self = castInterfaceParser(parser);
|
||||||
const context = self.castContext(ctx);
|
const context = self.castContext(ctx);
|
||||||
return try self.subparse(context, name, args, env);
|
return try self.subparse(context, name, args, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _wrapFinish(parser: *anyopaque, ctx: *anyopaque) anyerror!?ParserInterface {
|
fn _wrapFinish(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
|
||||||
const self = castInterfaceParser(parser);
|
const self = castInterfaceParser(parser);
|
||||||
const context = self.castContext(ctx);
|
const context = self.castContext(ctx);
|
||||||
return try self.finish(context);
|
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 {
|
fn _wrapAddSubcommand(parser: *anyopaque, name: []const u8, subcommand: ParserInterface) !void {
|
||||||
const self = castInterfaceParser(parser);
|
const self = castInterfaceParser(parser);
|
||||||
return self.addSubcommand(name, subcommand);
|
return self.addSubcommand(name, subcommand);
|
||||||
|
Reference in New Issue
Block a user