Compare commits

...

10 Commits

Author SHA1 Message Date
80c4853171
style: use conventional camelCase naming for functions
I think I still prefer snake_case stylistically, but this style fits in
a lot better with other zig code and is the officially endorsed style,
so it makes sense to use it. Since I don't have good test coverage,
some conversions may have been missed, but the demo still builds,
which is a decent amount of coverage. This was pretty easy to do with
a find and replace, so it should be reasonably thorough.
2023-08-05 13:41:21 -07:00
883218cdca
parser: add interface method for retrieving a child interface by name
The main value of this method is that it allows runtime access to the
help description of the subcommand. This could allow implementation of
a help flag that takes the name of a subcommand to print help for or
something. Anyway, it's probably useful.
2023-08-04 00:18:38 -07:00
d091de5686
command: add missing ptrCast
While a lot of values will implicitly coerce to this field value,
slices annoyingly do not and thus the explicit cast is required.
2023-08-04 00:15:29 -07:00
29175d07ce
command: add a method for creating owned interfaces
This allocates the interface with its own arena allocator, allowing it
to live beyond its stack lifetime. This enables some useful patterns
for composing a CLI from multiple functions or files. This is actually
probably the preferred method over `create_parser` in most
circumstances.
2023-08-04 00:14:40 -07:00
86342bcd1f
help: print byte slice defaults as strings
There are a couple of other places where []u8 is treated implicitly
like a string, which isn't strictly correct. Ultimately, some kind of
metasignal will be required to make this type truly unambiguous in
interpretation.
2023-08-04 00:12:26 -07:00
adf05ca489
readme: zig 0.11 is out 2023-08-04 00:08:12 -07:00
71653858ab
build: update for zig 0.11 and format
There was an API change to addInstallArtifact. It now takes a second
argument.
2023-08-04 00:07:41 -07:00
d1803284b4
noclip: repub imports
As far as I can tell, there's no good reason not to do this.
2023-07-31 12:40:37 -07:00
5f0d7b34d7
parser: shove an arena allocator in there
Stay a while and listen to my story.

Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.

Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.

The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).

In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
efbc6e7b66
all: update for changed zig builtins
enumToInt changed to intFromEnum, and the casting builtins figured out
how to automagically infer the cast type. This results in some minor
simplification, which is nice.
2023-07-19 00:32:14 -07:00
10 changed files with 360 additions and 269 deletions

View File

@ -5,7 +5,7 @@ pub fn build(b: *std.Build) void {
const optimize: std.builtin.Mode = b.standardOptimizeOption(.{});
const noclip = b.addModule("noclip", .{
.source_file = .{ .path = "source/noclip.zig" }
.source_file = .{ .path = "source/noclip.zig" },
});
demo(b, noclip, target, optimize);
@ -21,7 +21,12 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&tests.step);
}
fn demo(b: *std.Build, noclip: *std.Build.Module, target: std.zig.CrossTarget, optimize: std.builtin.Mode) void {
fn demo(
b: *std.Build,
noclip: *std.Build.Module,
target: std.zig.CrossTarget,
optimize: std.builtin.Mode,
) void {
const demo_step = b.step("demo", "Build and install CLI demo program");
const exe = b.addExecutable(.{
@ -31,7 +36,7 @@ fn demo(b: *std.Build, noclip: *std.Build.Module, target: std.zig.CrossTarget, o
.optimize = optimize,
});
exe.addModule("noclip", noclip);
const install_demo = b.addInstallArtifact(exe);
const install_demo = b.addInstallArtifact(exe, .{});
demo_step.dependOn(&install_demo.step);
}

View File

@ -13,7 +13,7 @@ const cli = cmd: {
\\This command demonstrates the functionality of the noclip library. cool!
,
};
cmd.add_option(.{ .OutputType = struct { u8, u8 } }, .{
cmd.addOption(.{ .OutputType = struct { u8, u8 } }, .{
.name = "test",
.short_tag = "-t",
.long_tag = "--test",
@ -21,7 +21,7 @@ const cli = cmd: {
.description = "multi-value test option",
.nice_type_name = "int> <int",
});
cmd.add_option(.{ .OutputType = Choice }, .{
cmd.addOption(.{ .OutputType = Choice }, .{
.name = "choice",
.short_tag = "-c",
.long_tag = "--choice",
@ -30,7 +30,7 @@ const cli = cmd: {
.description = "enum choice option",
.nice_type_name = "choice",
});
cmd.add_option(.{ .OutputType = u32 }, .{
cmd.addOption(.{ .OutputType = u32 }, .{
.name = "default",
.short_tag = "-d",
.long_tag = "--default",
@ -39,33 +39,29 @@ const cli = cmd: {
.description = "default value integer option",
.nice_type_name = "uint",
});
cmd.add_option(.{ .OutputType = u8, .multi = true }, .{
cmd.addOption(.{ .OutputType = u8, .multi = true }, .{
.name = "multi",
.short_tag = "-m",
.long_tag = "--multi",
.description = "multiple specification test option",
});
cmd.add_flag(.{}, .{
cmd.addFlag(.{}, .{
.name = "flag",
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
.falsy = .{ .short_tag = "-F", .long_tag = "--no-flag" },
.env_var = "NOCLIP_FLAG",
.description = "boolean flag",
});
cmd.add_flag(.{ .multi = true }, .{
cmd.addFlag(.{ .multi = true }, .{
.name = "multiflag",
.truthy = .{ .short_tag = "-M" },
.description = "multiple specification test flag ",
});
cmd.add_option(.{ .OutputType = u8 }, .{
cmd.addOption(.{ .OutputType = u8 }, .{
.name = "env",
.env_var = "NOCLIP_ENVIRON",
.description = "environment variable only option",
});
cmd.add_argument(.{ .OutputType = []const u8 }, .{
.name = "arg",
.description = "This is an argument that doesn't really do anything, but it's very important.",
});
break :cmd cmd;
};
@ -73,27 +69,31 @@ const cli = cmd: {
const subcommand = cmd: {
var cmd = CommandBuilder([]const u8){
.description =
\\Perform some sort of work
\\Demonstrate subcommand functionality
\\
\\This subcommand is a mystery. It probably does something, but nobody is sure what.
\\This command demonstrates how subcommands work.
,
};
cmd.simple_flag(.{
cmd.simpleFlag(.{
.name = "flag",
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
.falsy = .{ .long_tag = "--no-flag" },
.env_var = "NOCLIP_SUBFLAG",
});
cmd.add_argument(.{ .OutputType = []const u8 }, .{ .name = "argument" });
cmd.addArgument(.{ .OutputType = []const u8 }, .{ .name = "argument" });
cmd.addArgument(.{ .OutputType = []const u8 }, .{
.name = "arg",
.description = "This is an argument that doesn't really do anything, but it's very important.",
});
break :cmd cmd;
};
fn sub_handler(context: []const u8, result: subcommand.Output()) !void {
fn subHandler(context: []const u8, result: subcommand.Output()) !void {
std.debug.print("subcommand: {s}\n", .{result.argument});
std.debug.print("context: {s}\n", .{context});
}
fn cli_handler(context: *u32, result: cli.Output()) !void {
fn cliHandler(context: *u32, result: cli.Output()) !void {
std.debug.print("context: {d}\n", .{context.*});
std.debug.print("callback is working {any}\n", .{result.choice});
std.debug.print("callback is working {d}\n", .{result.default});
@ -101,16 +101,18 @@ fn cli_handler(context: *u32, result: cli.Output()) !void {
}
pub fn main() !u8 {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var parser = try cli.createParser(cliHandler, allocator);
defer parser.deinitTree();
var parser = cli.create_parser(cli_handler, allocator);
var context: u32 = 2;
const sc: []const u8 = "whassup";
var subcon = subcommand.create_parser(sub_handler, allocator);
try parser.add_subcommand("verb", subcon.interface(&sc));
var subcon = try subcommand.createParser(subHandler, allocator);
try parser.addSubcommand("verb", subcon.interface(&sc));
const iface = parser.interface(&context);
iface.execute() catch return 1;

View File

@ -22,8 +22,7 @@ ____
== Hello
Requires a pre-release version of Zig `0.11.0`. `0.11.0-dev.1844+75ff34db9` is the
oldest version specifically known to me to work.
Requires Zig `0.11.x`.
=== Features
@ -40,7 +39,7 @@ and bugs likely exist.
* arbitrarily nestable subcommands for building sophisticated tools
* parser that supports many conventional CLI behaviors
** congealed short flags (e.g. `-abc` is the same as `-a -b -c`)
** `--` to force early of argument parsing
** `--` to force early end of flag parsing
** both `--long value` and `--long=value` styles are supported
* option values are converted into rich native types using conversion callback functions
** integers

View File

@ -12,8 +12,8 @@ const OptionConfig = parameters.OptionConfig;
const FlagConfig = parameters.FlagConfig;
const ShortLongPair = parameters.ShortLongPair;
const FlagBias = parameters.FlagBias;
const make_option = parameters.make_option;
const make_argument = parameters.make_argument;
const makeOption = parameters.makeOption;
const makeArgument = parameters.makeArgument;
const Parser = parser.Parser;
const ParserInterface = parser.ParserInterface;
@ -24,7 +24,7 @@ fn BuilderGenerics(comptime UserContext: type) type {
value_count: ValueCount = .{ .fixed = 1 },
multi: bool = false,
pub fn arg_gen(comptime self: @This()) ParameterGenerics {
pub fn argGen(comptime self: @This()) ParameterGenerics {
if (self.value_count == .flag) @compileError("argument may not be a flag");
if (self.value_count == .count) @compileError("argument may not be a count");
@ -32,12 +32,12 @@ fn BuilderGenerics(comptime UserContext: type) type {
.UserContext = UserContext,
.OutputType = self.OutputType,
.param_type = .Ordinal,
.value_count = ParameterGenerics.fixed_value_count(self.OutputType, self.value_count),
.value_count = ParameterGenerics.fixedValueCount(self.OutputType, self.value_count),
.multi = self.multi,
};
}
pub fn opt_gen(comptime self: @This()) ParameterGenerics {
pub fn optGen(comptime self: @This()) ParameterGenerics {
if (self.value_count == .flag) @compileError("option may not be a flag");
if (self.value_count == .count) @compileError("option may not be a count");
@ -45,12 +45,12 @@ fn BuilderGenerics(comptime UserContext: type) type {
.UserContext = UserContext,
.OutputType = self.OutputType,
.param_type = .Nominal,
.value_count = ParameterGenerics.fixed_value_count(self.OutputType, self.value_count),
.value_count = ParameterGenerics.fixedValueCount(self.OutputType, self.value_count),
.multi = self.multi,
};
}
pub fn count_gen(comptime _: @This()) ParameterGenerics {
pub fn countGen(comptime _: @This()) ParameterGenerics {
return ParameterGenerics{
.UserContext = UserContext,
.OutputType = usize,
@ -60,7 +60,7 @@ fn BuilderGenerics(comptime UserContext: type) type {
};
}
pub fn flag_gen(comptime self: @This()) ParameterGenerics {
pub fn flagGen(comptime self: @This()) ParameterGenerics {
return ParameterGenerics{
.UserContext = UserContext,
.OutputType = bool,
@ -83,19 +83,50 @@ pub fn CommandBuilder(comptime UserContext: type) type {
pub const UserContextType = UserContext;
pub fn create_parser(
pub fn createParser(
comptime self: @This(),
comptime callback: self.CallbackSignature(),
allocator: std.mem.Allocator,
) Parser(self, callback) {
) !Parser(self, callback) {
// note: this is freed in Parser.deinit
var arena = try allocator.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(allocator);
const arena_alloc = arena.allocator();
return Parser(self, callback){
.allocator = allocator,
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(allocator),
.help_builder = help.HelpBuilder(self).init(allocator),
.arena = arena,
.allocator = arena_alloc,
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(arena_alloc),
.help_builder = help.HelpBuilder(self).init(arena_alloc),
};
}
pub fn set_help_flag(
pub fn createInterface(
comptime self: @This(),
comptime callback: self.CallbackSignature(),
allocator: std.mem.Allocator,
context: UserContextType,
) !ParserInterface {
var arena = try allocator.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(allocator);
const arena_alloc = arena.allocator();
var this_parser = try arena_alloc.create(Parser(self, callback));
this_parser.* = .{
.arena = arena,
.allocator = arena_alloc,
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(arena_alloc),
.help_builder = help.HelpBuilder(self).init(arena_alloc),
};
if (UserContextType == void) {
return this_parser.interface();
} else {
return this_parser.interface(context);
}
}
pub fn setHelpFlag(
comptime self: *@This(),
comptime tags: ShortLongPair,
) void {
@ -104,49 +135,49 @@ pub fn CommandBuilder(comptime UserContext: type) type {
const string_generics = BuilderGenerics(UserContext){ .OutputType = []const u8 };
pub fn string_option(
pub fn stringOption(
comptime self: *@This(),
comptime cfg: OptionConfig(string_generics.opt_gen()),
comptime cfg: OptionConfig(string_generics.optGen()),
) void {
const config = if (cfg.nice_type_name == null)
ncmeta.copy_struct(@TypeOf(cfg), cfg, .{ .nice_type_name = "string" })
ncmeta.copyStruct(@TypeOf(cfg), cfg, .{ .nice_type_name = "string" })
else
cfg;
self.add_option(string_generics, config);
self.addOption(string_generics, config);
}
pub fn string_argument(
pub fn stringArgument(
comptime self: *@This(),
comptime cfg: OptionConfig(string_generics.arg_gen()),
comptime cfg: OptionConfig(string_generics.argGen()),
) void {
const config = if (cfg.nice_type_name == null)
ncmeta.copy_struct(@TypeOf(cfg), cfg, .{ .nice_type_name = "string" })
ncmeta.copyStruct(@TypeOf(cfg), cfg, .{ .nice_type_name = "string" })
else
cfg;
self.add_argument(string_generics, config);
self.addArgument(string_generics, config);
}
pub fn simple_flag(
pub fn simpleFlag(
comptime self: *@This(),
comptime cfg: FlagConfig(string_generics.flag_gen()),
comptime cfg: FlagConfig(string_generics.flagGen()),
) void {
self.add_flag(string_generics, cfg);
self.addFlag(string_generics, cfg);
}
pub fn add_argument(
pub fn addArgument(
comptime self: *@This(),
comptime bgen: BuilderGenerics(UserContext),
comptime config: OptionConfig(bgen.arg_gen()),
comptime config: OptionConfig(bgen.argGen()),
) void {
self.param_spec.add(make_argument(bgen.arg_gen(), config));
self.param_spec.add(makeArgument(bgen.argGen(), config));
}
pub fn add_option(
pub fn addOption(
comptime self: *@This(),
comptime bgen: BuilderGenerics(UserContext),
comptime config: OptionConfig(bgen.opt_gen()),
comptime config: OptionConfig(bgen.optGen()),
) void {
if (comptime bgen.value_count == .fixed and bgen.value_count.fixed == 0) {
@compileError(
@ -155,13 +186,13 @@ pub fn CommandBuilder(comptime UserContext: type) type {
);
}
self.param_spec.add(make_option(bgen.opt_gen(), config));
self.param_spec.add(makeOption(bgen.optGen(), config));
}
pub fn add_flag(
pub fn addFlag(
comptime self: *@This(),
comptime bgen: BuilderGenerics(UserContext),
comptime config: FlagConfig(bgen.flag_gen()),
comptime config: FlagConfig(bgen.flagGen()),
) void {
comptime {
if (config.truthy == null and config.falsy == null and config.env_var == null) {
@ -172,7 +203,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
);
}
const generics = bgen.flag_gen();
const generics = bgen.flagGen();
var args = OptionConfig(generics){
.name = config.name,
//
@ -205,7 +236,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
args.long_tag = truthy_pair.long_tag;
args.flag_bias = .truthy;
self.param_spec.add(make_option(generics, args));
self.param_spec.add(makeOption(generics, args));
}
if (config.falsy) |falsy_pair| {
@ -221,7 +252,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
args.long_tag = falsy_pair.long_tag;
args.flag_bias = .falsy;
self.param_spec.add(make_option(generics, args));
self.param_spec.add(makeOption(generics, args));
}
if (config.env_var) |env_var| {
@ -231,7 +262,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
args.env_var = env_var;
args.flag_bias = .unbiased;
self.param_spec.add(make_option(generics, args));
self.param_spec.add(makeOption(generics, args));
}
}
}
@ -296,16 +327,16 @@ pub fn CommandBuilder(comptime UserContext: type) type {
if (PType.is_flag) {
var peek = idx + 1;
var bias_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias);
bias_seen[@enumToInt(param.flag_bias)] = true;
var bias_seen: [ncmeta.enumLength(FlagBias)]bool = [_]bool{false} ** ncmeta.enumLength(FlagBias);
bias_seen[@intFromEnum(param.flag_bias)] = true;
while (peek < spec.len) : (peek += 1) {
const peek_param = spec[peek];
if (@TypeOf(peek_param).is_flag and std.mem.eql(u8, param.name, peek_param.name)) {
if (bias_seen[@enumToInt(peek_param.flag_bias)] == true) {
if (bias_seen[@intFromEnum(peek_param.flag_bias)] == true) {
@compileError("redundant flag!!!! " ++ param.name);
} else {
bias_seen[@enumToInt(peek_param.flag_bias)] = true;
bias_seen[@intFromEnum(peek_param.flag_bias)] = true;
}
flag_skip += 1;
} else {
@ -327,7 +358,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
fields = fields ++ &[_]StructField{.{
.name = param.name,
.type = FieldType,
.default_value = default,
.default_value = @ptrCast(default),
.is_comptime = false,
.alignment = @alignOf(FieldType),
}};
@ -371,16 +402,16 @@ pub fn CommandBuilder(comptime UserContext: type) type {
const PType = @TypeOf(param);
if (PType.is_flag) {
var peek = idx + 1;
var bias_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias);
bias_seen[@enumToInt(param.flag_bias)] = true;
var bias_seen: [ncmeta.enumLength(FlagBias)]bool = [_]bool{false} ** ncmeta.enumLength(FlagBias);
bias_seen[@intFromEnum(param.flag_bias)] = true;
while (peek < spec.len) : (peek += 1) {
const peek_param = spec[peek];
if (@TypeOf(peek_param).is_flag and std.mem.eql(u8, param.name, peek_param.name)) {
if (bias_seen[@enumToInt(peek_param.flag_bias)] == true) {
if (bias_seen[@intFromEnum(peek_param.flag_bias)] == true) {
@compileError("redundant flag!!!! " ++ param.name);
} else {
bias_seen[@enumToInt(peek_param.flag_bias)] = true;
bias_seen[@intFromEnum(peek_param.flag_bias)] = true;
}
flag_skip += 1;
} else {
@ -397,13 +428,10 @@ pub fn CommandBuilder(comptime UserContext: type) type {
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
.name = param.name,
.type = FieldType,
.default_value = @ptrCast(
?*const anyopaque,
&@as(
FieldType,
if (PType.value_count == .count) 0 else null,
),
),
.default_value = @ptrCast(&@as(
FieldType,
if (PType.value_count == .count) 0 else null,
)),
.is_comptime = false,
.alignment = @alignOf(?[]const u8),
}});

View File

@ -17,30 +17,30 @@ pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
) ConversionError!gen.ConvertedType();
}
pub fn default_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
pub fn DefaultConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
return if (comptime gen.multi)
multi_converter(gen)
MultiConverter(gen)
else switch (@typeInfo(gen.OutputType)) {
.Bool => flag_converter(gen),
.Int => int_converter(gen),
.Bool => FlagConverter(gen),
.Int => IntConverter(gen),
.Pointer => |info| if (info.size == .Slice and info.child == u8)
string_converter(gen)
StringConverter(gen)
else
null,
.Enum => |info| if (info.is_exhaustive) choice_converter(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
// 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_converter(gen)
StructConverter(gen)
else
null,
else => null,
};
}
fn multi_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
const converter = default_converter(
ncmeta.copy_struct(ParameterGenerics, gen, .{ .multi = false }),
fn MultiConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
const converter = DefaultConverter(
ncmeta.copyStruct(ParameterGenerics, gen, .{ .multi = false }),
) orelse
@compileError("no default converter");
const Intermediate = gen.IntermediateType();
@ -59,7 +59,7 @@ fn multi_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
}.handler;
}
fn flag_converter(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 {
// treat an empty string as falsy
@ -79,7 +79,7 @@ fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
}.handler;
}
fn string_converter(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 {
return input;
@ -87,7 +87,7 @@ fn string_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
}.handler;
}
fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
fn IntConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const IntType = gen.OutputType;
return struct {
@ -100,7 +100,7 @@ fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
}.handler;
}
fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
fn StructConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const StructType = gen.OutputType;
const type_info = @typeInfo(StructType).Struct;
const Intermediate = gen.IntermediateType();
@ -117,15 +117,15 @@ fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
var result: StructType = undefined;
inline for (comptime type_info.fields, 0..) |field, idx| {
const converter = comptime default_converter(
ncmeta.copy_struct(ParameterGenerics, gen, .{
const Converter = comptime DefaultConverter(
ncmeta.copyStruct(ParameterGenerics, gen, .{
.OutputType = field.type,
.value_count = .{ .fixed = 1 },
}),
) orelse
@compileError("cannot get converter for field" ++ field.name);
@field(result, field.name) = try converter(context, input.items[idx], failure);
@field(result, field.name) = try Converter(context, input.items[idx], failure);
}
return result;
@ -133,7 +133,7 @@ fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
}.handler;
}
fn choice_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
fn ChoiceConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const EnumType = gen.OutputType;
return struct {

View File

@ -20,7 +20,7 @@ pub fn StructuredPrinter(comptime Writer: type) type {
wrap_width: usize = 100,
writer: Writer,
pub fn print_pair(self: *@This(), pair: AlignablePair, leading_indent: u8, tabstop: usize) !void {
pub fn printPair(self: *@This(), pair: AlignablePair, leading_indent: u8, tabstop: usize) !void {
try self.writer.writeByteNTimes(' ', leading_indent);
const left = std.mem.trim(u8, pair.left, " \n");
try self.writer.writeAll(left);
@ -30,26 +30,26 @@ pub fn StructuredPrinter(comptime Writer: type) type {
if (offset > tabstop) return NoclipError.UnexpectedFailure;
try self.writer.writeByteNTimes(' ', tabstop - offset);
try self.print_rewrap(std.mem.trim(u8, pair.right, " \n"), tabstop);
try self.printRewrap(std.mem.trim(u8, pair.right, " \n"), tabstop);
try self.writer.writeByte('\n');
}
pub fn print_pair_brief(self: *@This(), pair: AlignablePair, leading_indent: u8, tabstop: usize) !void {
pub fn printPairBrief(self: *@This(), pair: AlignablePair, leading_indent: u8, tabstop: usize) !void {
const brief = ncmeta.partition(u8, pair.right, &[_][]const u8{"\n\n"})[0];
const simulacrum: AlignablePair = .{
.left = pair.left,
.right = brief,
};
try self.print_pair(simulacrum, leading_indent, tabstop);
try self.printPair(simulacrum, leading_indent, tabstop);
}
pub fn print_wrapped(self: *@This(), text: []const u8, leading_indent: usize) !void {
pub fn printWrapped(self: *@This(), text: []const u8, leading_indent: usize) !void {
try self.writer.writeByteNTimes(' ', leading_indent);
try self.print_rewrap(std.mem.trim(u8, text, "\n"), leading_indent);
try self.printRewrap(std.mem.trim(u8, text, "\n"), leading_indent);
}
fn print_rewrap(self: *@This(), text: []const u8, indent: usize) !void {
fn printRewrap(self: *@This(), text: []const u8, indent: usize) !void {
// TODO: lol return a real error
if (indent >= self.wrap_width) return NoclipError.UnexpectedFailure;
@ -62,8 +62,8 @@ pub fn StructuredPrinter(comptime Writer: type) type {
if (line.len == 0) {
// we have a trailing line that needs to be cleaned up
if (location > indent)
_ = try self.clear_line(indent);
location = try self.clear_line(indent);
_ = try self.clearLine(indent);
location = try self.clearLine(indent);
continue;
}
@ -91,7 +91,7 @@ pub fn StructuredPrinter(comptime Writer: type) type {
break;
}
if (location != indent)
location = try self.clear_line(indent);
location = try self.clearLine(indent);
need_forced_break = true;
continue :choppa;
@ -100,13 +100,13 @@ pub fn StructuredPrinter(comptime Writer: type) type {
if (location > indent)
try self.writer.writeByte(' ');
try self.writer.writeAll(choppee[0..split]);
location = try self.clear_line(indent);
location = try self.clearLine(indent);
choppee = choppee[split + 1 ..];
}
}
}
fn clear_line(self: *@This(), indent: usize) !usize {
fn clearLine(self: *@This(), indent: usize) !usize {
try self.writer.writeByte('\n');
try self.writer.writeByteNTimes(' ', indent);
return indent;
@ -115,7 +115,7 @@ pub fn StructuredPrinter(comptime Writer: type) type {
}
pub fn HelpBuilder(comptime command: anytype) type {
const help_info = opt_info(command.generate());
const help_info = optInfo(command.generate());
return struct {
writebuffer: std.ArrayList(u8),
@ -126,7 +126,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
};
}
pub fn build_message(
pub fn buildMessage(
self: *@This(),
name: []const u8,
subcommands: parser.CommandMap,
@ -136,26 +136,26 @@ pub fn HelpBuilder(comptime command: anytype) type {
"Usage: {s}{s}{s}{s}\n\n",
.{
name,
self.option_brief(),
try self.args_brief(),
self.subcommands_brief(subcommands),
self.optionBrief(),
try self.argsBrief(),
self.subcommandsBrief(subcommands),
},
);
var printer = StructuredPrinter(@TypeOf(writer)){ .writer = writer };
try printer.print_wrapped(command.description, 2);
try printer.printWrapped(command.description, 2);
try writer.writeAll("\n\n");
const arguments = try self.describe_arguments();
const options = try self.describe_options();
const env_vars = try self.describe_env();
const subcs = try self.describe_subcommands(subcommands);
const arguments = try self.describeArguments();
const options = try self.describeOptions();
const env_vars = try self.describeEnv();
const subcs = try self.describeSubcommands(subcommands);
const max_just = @max(arguments.just, @max(options.just, @max(env_vars.just, subcs.just)));
if (arguments.pairs.len > 0) {
try writer.writeAll("Arguments:\n");
for (arguments.pairs) |pair|
try printer.print_pair(pair, 2, max_just + 4);
try printer.printPair(pair, 2, max_just + 4);
try writer.writeAll("\n");
}
@ -163,7 +163,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
if (options.pairs.len > 0) {
try writer.writeAll("Options:\n");
for (options.pairs) |pair|
try printer.print_pair(pair, 2, max_just + 4);
try printer.printPair(pair, 2, max_just + 4);
try writer.writeAll("\n");
}
@ -171,7 +171,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
if (env_vars.pairs.len > 0) {
try writer.writeAll("Environment variables:\n");
for (env_vars.pairs) |pair|
try printer.print_pair(pair, 2, max_just + 4);
try printer.printPair(pair, 2, max_just + 4);
try writer.writeAll("\n");
}
@ -179,7 +179,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
if (subcs.pairs.len > 0) {
try writer.writeAll("Subcommands:\n");
for (subcs.pairs) |pair|
try printer.print_pair_brief(pair, 2, max_just + 4);
try printer.printPairBrief(pair, 2, max_just + 4);
try writer.writeAll("\n");
}
@ -187,14 +187,14 @@ pub fn HelpBuilder(comptime command: anytype) type {
return self.writebuffer.toOwnedSlice();
}
fn option_brief(_: @This()) []const u8 {
fn optionBrief(_: @This()) []const u8 {
return if (comptime help_info.options.len > 0)
" [options...]"
else
"";
}
fn args_brief(self: @This()) ![]const u8 {
fn argsBrief(self: @This()) ![]const u8 {
var buf = std.ArrayList(u8).init(self.writebuffer.allocator);
defer buf.deinit();
@ -214,19 +214,19 @@ pub fn HelpBuilder(comptime command: anytype) type {
return buf.toOwnedSlice();
}
fn subcommands_brief(_: @This(), subcommands: parser.CommandMap) []const u8 {
fn subcommandsBrief(_: @This(), subcommands: parser.CommandMap) []const u8 {
return if (subcommands.count() > 0)
" <subcommand ...>"
else
"";
}
fn describe_arguments(self: @This()) !OptionDescription {
fn describeArguments(self: @This()) !OptionDescription {
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
defer pairs.deinit();
var just: usize = 0;
inline for (comptime help_info.arguments) |arg| {
inline for (comptime help_info.arguments) |arg| {
if (comptime arg.description.len == 0) continue;
const pair: AlignablePair = .{
@ -243,13 +243,13 @@ pub fn HelpBuilder(comptime command: anytype) type {
};
}
fn describe_options(self: @This()) !OptionDescription {
fn describeOptions(self: @This()) !OptionDescription {
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
defer pairs.deinit();
var just: usize = 0;
inline for (help_info.options) |opt| {
const pair = try self.describe_option(opt);
const pair = try self.describeOption(opt);
if (pair.left.len > just) just = pair.left.len;
try pairs.append(pair);
}
@ -260,7 +260,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
};
}
fn describe_option(self: @This(), comptime opt: OptHelp) !AlignablePair {
fn describeOption(self: @This(), comptime opt: OptHelp) !AlignablePair {
var buffer = std.ArrayList(u8).init(self.writebuffer.allocator);
defer buffer.deinit();
const writer = buffer.writer();
@ -321,7 +321,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
return .{ .left = left, .right = right };
}
fn describe_env(self: @This()) !OptionDescription {
fn describeEnv(self: @This()) !OptionDescription {
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
defer pairs.deinit();
@ -343,7 +343,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
};
}
fn describe_subcommands(self: @This(), subcommands: parser.CommandMap) !OptionDescription {
fn describeSubcommands(self: @This(), subcommands: parser.CommandMap) !OptionDescription {
var pairs = std.ArrayList(AlignablePair).init(self.writebuffer.allocator);
defer pairs.deinit();
@ -402,7 +402,7 @@ const ArgHelp = struct {
required: bool = true,
};
pub fn opt_info(comptime command: anytype) CommandHelp {
pub fn optInfo(comptime command: anytype) CommandHelp {
// TODO: this could be runtime and it would be slightly simpler.
comptime {
var options: []const OptHelp = &[_]OptHelp{};
@ -428,7 +428,7 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
if (!std.mem.eql(u8, last_name, param.name)) {
if (last_name.len > 0) {
if (env_only(last_option)) {
if (envOnly(last_option)) {
env_vars = env_vars ++ &[_]EnvHelp{.{
.env_var = last_option.env_var,
.description = last_option.description,
@ -471,13 +471,23 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
// TODO: this is only acceptable for some types. It behaves poorly on
// enum-based choice types because it prints the whole type name rather
// than just the tag name. Roll our own eventually.
writer.print("{any}", .{def}) catch @compileError("whoah");
blk: {
switch (@typeInfo(@TypeOf(def))) {
.Pointer => |info| if (info.size == .Slice and info.child == u8) {
writer.print("{s}", .{def}) catch @compileError("no");
break :blk;
},
else => {},
}
writer.print("{any}", .{def}) catch @compileError("whoah");
}
last_option.default = buf.buffer;
}
}
if (last_name.len > 0) {
if (env_only(last_option)) {
if (envOnly(last_option)) {
env_vars = env_vars ++ &[_]EnvHelp{.{
.env_var = last_option.env_var.?,
.description = last_option.description,
@ -496,7 +506,7 @@ pub fn opt_info(comptime command: anytype) CommandHelp {
}
}
inline fn env_only(option: OptHelp) bool {
inline fn envOnly(option: OptHelp) bool {
return option.short_truthy == null and
option.long_truthy == null and
option.short_falsy == null and

View File

@ -30,7 +30,7 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
// the appropriate type, which is nice for ergonomics. Not sure
// if it introduces weird edge cases. Probably it's fine?
.default_value = if (@hasField(@TypeOf(defaults), field.name))
@ptrCast(?*const anyopaque, &@as(field.field_type, @field(defaults, field.name)))
@ptrCast(&@as(field.field_type, @field(defaults, field.name)))
else
field.default_value,
.is_comptime = field.is_comptime,
@ -48,7 +48,7 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
}
}
pub fn enum_length(comptime T: type) comptime_int {
pub fn enumLength(comptime T: type) comptime_int {
return @typeInfo(T).Enum.fields.len;
}
@ -174,7 +174,7 @@ pub fn SliceIterator(comptime T: type) type {
};
}
pub fn copy_struct(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;
comptime inline for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
@ -203,7 +203,7 @@ pub const TupleBuilder = struct {
}
pub fn retrieve(comptime self: @This(), comptime index: comptime_int) self.types[index] {
return @ptrCast(*const self.types[index], @alignCast(@alignOf(*const self.types[index]), self.pointers[index])).*;
return @as(*const self.types[index], @ptrCast(@alignCast(self.pointers[index]))).*;
}
pub fn realTuple(comptime self: @This()) self.TupleType() {

View File

@ -1,9 +1,10 @@
const command = @import("./command.zig");
const converters = @import("./converters.zig");
const errors = @import("./errors.zig");
const help = @import("./help.zig");
const ncmeta = @import("./meta.zig");
const parameters = @import("./parameters.zig");
const parser = @import("./parser.zig");
pub const command = @import("./command.zig");
pub const converters = @import("./converters.zig");
pub const errors = @import("./errors.zig");
pub const help = @import("./help.zig");
pub const ncmeta = @import("./meta.zig");
pub const parameters = @import("./parameters.zig");
pub const parser = @import("./parser.zig");
pub const CommandBuilder = command.CommandBuilder;
pub const ParserInterface = parser.ParserInterface;

View File

@ -34,6 +34,7 @@ pub const ParameterGenerics = struct {
/// useful for implementing complex conversion that produces output through its
/// side effects or by modifying the user context.
OutputType: type = void,
param_type: ParameterType,
value_count: ValueCount,
/// allow this named parameter to be passed multiple times.
@ -44,7 +45,7 @@ pub const ParameterGenerics = struct {
// many-to-many and the many-to-one cases.
multi: bool,
pub fn fixed_value_count(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)
switch (@typeInfo(OutputType)) {
.Struct => |info| .{ .fixed = info.fields.len },
@ -58,15 +59,15 @@ pub const ParameterGenerics = struct {
value_count;
}
pub fn has_context(comptime self: @This()) bool {
pub fn hasContext(comptime self: @This()) bool {
return comptime self.UserContext != void;
}
pub fn has_output(comptime self: @This()) bool {
pub fn hasOutput(comptime self: @This()) bool {
return self.OutputType != void;
}
pub fn is_flag(comptime self: @This()) bool {
pub fn isFlag(comptime self: @This()) bool {
return comptime switch (self.value_count) {
.flag, .count => true,
.fixed => false,
@ -185,10 +186,10 @@ fn OptionType(comptime generics: ParameterGenerics) type {
return struct {
pub const G: ParameterGenerics = generics;
pub const param_type: ParameterType = generics.param_type;
pub const is_flag: bool = generics.is_flag();
pub const is_flag: bool = generics.isFlag();
pub const value_count: ValueCount = generics.value_count;
pub const multi: bool = generics.multi;
pub const has_output: bool = generics.has_output();
pub const has_output: bool = generics.hasOutput();
name: []const u8,
short_tag: ?[]const u8,
@ -228,17 +229,17 @@ fn OptionType(comptime generics: ParameterGenerics) type {
};
}
fn check_short(comptime short_tag: ?[]const u8) void {
fn checkShort(comptime short_tag: ?[]const u8) void {
const short = comptime short_tag orelse return;
if (short.len != 2 or short[0] != '-') @compileError("bad short tag: " ++ short);
}
fn check_long(comptime long_tag: ?[]const u8) void {
fn checkLong(comptime long_tag: ?[]const u8) void {
const long = comptime long_tag orelse return;
if (long.len < 3 or long[0] != '-' or long[1] != '-') @compileError("bad long tag: " ++ long);
}
pub fn make_option(comptime generics: ParameterGenerics, comptime opts: OptionConfig(generics)) OptionType(generics) {
pub fn makeOption(comptime generics: ParameterGenerics, comptime opts: OptionConfig(generics)) OptionType(generics) {
if (opts.short_tag == null and opts.long_tag == null and opts.env_var == null) {
@compileError(
"option " ++
@ -247,8 +248,8 @@ pub fn make_option(comptime generics: ParameterGenerics, comptime opts: OptionCo
);
}
check_short(opts.short_tag);
check_long(opts.long_tag);
checkShort(opts.short_tag);
checkLong(opts.long_tag);
// perform the logic to create the default converter here? Could be done
// when creating the OptionConfig instead. Need to do it here because there
@ -257,7 +258,7 @@ pub fn make_option(comptime generics: ParameterGenerics, comptime opts: OptionCo
// whereas the OptionType is an instance of an object that has been
// validated.
const converter = opts.converter orelse
(converters.default_converter(generics) orelse @compileError(
(converters.DefaultConverter(generics) orelse @compileError(
"no converter provided for " ++
opts.name ++
"and no default exists",
@ -284,7 +285,7 @@ pub fn make_option(comptime generics: ParameterGenerics, comptime opts: OptionCo
};
}
pub fn make_argument(
pub fn makeArgument(
comptime generics: ParameterGenerics,
comptime opts: OptionConfig(generics),
) OptionType(generics) {
@ -298,7 +299,7 @@ pub fn make_argument(
}
const converter = opts.converter orelse
(converters.default_converter(generics) orelse @compileError(
(converters.DefaultConverter(generics) orelse @compileError(
"no converter provided for " ++
opts.name ++
"and no default exists",

View File

@ -12,13 +12,32 @@ pub const ParserInterface = 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,
describe: *const fn () []const u8,
deinit: *const fn (parser: *anyopaque) void,
deinitTree: *const fn (parser: *anyopaque) void,
};
parser: *anyopaque,
context: *anyopaque,
methods: *const Vtable,
fn create(comptime ParserType: type, parser: *anyopaque, context: *anyopaque) @This() {
return .{
.parser = parser,
.context = context,
.methods = &.{
.execute = ParserType._wrapExecute,
.parse = ParserType._wrapParse,
.finish = ParserType._wrapFinish,
.getChild = ParserType._wrapGetChild,
.describe = ParserType._wrapDescribe,
.deinit = ParserType._wrapDeinit,
.deinitTree = ParserType._wrapDeinitTree,
},
};
}
pub fn execute(self: @This()) anyerror!void {
return try self.methods.execute(self.parser, self.context);
}
@ -31,64 +50,101 @@ pub const ParserInterface = struct {
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 describe(self: @This()) []const u8 {
return self.methods.describe();
}
pub fn deinit(self: @This()) void {
self.methods.deinit(self.parser);
}
pub fn deinitTree(self: @This()) void {
self.methods.deinitTree(self.parser);
}
};
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 .{
.parser = self,
.context = @constCast(&void{}),
.methods = &.{
.execute = ParserType.wrap_execute,
.parse = ParserType.wrap_parse,
.finish = ParserType.wrap_finish,
.describe = ParserType.describe,
},
};
return ParserInterface.create(ParserType, self, @constCast(&void{}));
}
fn cast_context(_: *anyopaque) 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 .{
.parser = self,
.context = @ptrCast(*anyopaque, @constCast(context)),
.methods = &.{
.execute = ParserType.wrap_execute,
.parse = ParserType.wrap_parse,
.finish = ParserType.wrap_finish,
.describe = ParserType.describe,
},
};
return ParserInterface.create(ParserType, self, @constCast(context));
}
fn cast_context(ctx: *anyopaque) UserContext {
return @ptrCast(UserContext, @alignCast(std.meta.alignment(UserContext), ctx));
fn castContext(_: ParserType, ctx: *anyopaque) UserContext {
return @ptrCast(@alignCast(ctx));
}
} else struct {
pub fn interface(self: *ParserType, context: *const UserContext) ParserInterface {
return .{
.parser = self,
.context = @ptrCast(*anyopaque, @constCast(context)),
.methods = &.{
.execute = ParserType.wrap_execute,
.parse = ParserType.wrap_parse,
.finish = ParserType.wrap_finish,
.describe = ParserType.describe,
},
};
return ParserInterface.create(ParserType, self, @ptrCast(@constCast(context)));
}
fn cast_context(ctx: *anyopaque) UserContext {
return @ptrCast(*const UserContext, @alignCast(@alignOf(UserContext), ctx)).*;
fn castContext(_: ParserType, ctx: *anyopaque) UserContext {
return @as(*const UserContext, @ptrCast(@alignCast(ctx))).*;
}
};
}
@ -104,58 +160,32 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
const Output = command.Output();
return struct {
const command_description = command.description;
intermediate: Intermediate = .{},
output: Output = undefined,
consumed_args: u32 = 0,
progname: ?[]const u8 = null,
has_global_tags: bool = false,
arena: *std.heap.ArenaAllocator,
allocator: std.mem.Allocator,
subcommands: CommandMap,
subcommand: ?ParserInterface = null,
help_builder: help.HelpBuilder(command),
pub fn add_subcommand(self: *@This(), verb: []const u8, parser: ParserInterface) !void {
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.
const Interface = InterfaceGen(@This(), UserContext);
pub usingnamespace Interface;
inline fn cast_interface_parser(parser: *anyopaque) *@This() {
return @ptrCast(*@This(), @alignCast(@alignOf(@This()), parser));
}
fn wrap_execute(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
const self = cast_interface_parser(parser);
// this is a slightly annoying hack to work around the problem that void has
// 0 alignment, which alignCast chokes on.
const context = Interface.cast_context(ctx);
return try self.execute(context);
}
fn wrap_parse(parser: *anyopaque, ctx: *anyopaque, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
const self = cast_interface_parser(parser);
const context = Interface.cast_context(ctx);
return try self.subparse(context, name, args, env);
}
fn wrap_finish(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
const self = cast_interface_parser(parser);
const context = Interface.cast_context(ctx);
return try self.finish(context);
}
fn describe() []const u8 {
return command.description;
}
pub usingnamespace InterfaceGen(@This(), UserContext);
pub usingnamespace InterfaceWrappers(@This());
pub fn subparse(self: *@This(), context: UserContext, name: []const u8, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
const sliceto = try self.parse(name, args);
try self.read_environment(env);
try self.convert_eager(context);
try self.readEnvironment(env);
try self.convertEager(context);
if (self.subcommand) |verb| {
const verbname = try std.mem.join(
@ -168,7 +198,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
const stderr = std.io.getStdErr().writer();
try stderr.writeAll("A subcommand is required.\n\n");
self.print_help(name);
self.printHelp(name);
}
}
@ -178,11 +208,26 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (self.subcommand) |verb| try verb.finish();
}
pub fn deinit(self: @This()) void {
self.arena.deinit();
self.arena.child_allocator.destroy(self.arena);
}
pub fn deinitTree(self: @This()) void {
var iterator = self.subcommands.valueIterator();
while (iterator.next()) |subcommand| {
subcommand.deinitTree();
}
self.deinit();
}
pub fn getChild(self: @This(), name: []const u8) ?ParserInterface {
return self.subcommands.get(name);
}
pub fn execute(self: *@This(), context: UserContext) anyerror!void {
const args = try std.process.argsAlloc(self.allocator);
defer std.process.argsFree(self.allocator, args);
var env = try std.process.getEnvMap(self.allocator);
defer env.deinit();
if (args.len < 1) return ParseError.EmptyArgs;
@ -192,11 +237,11 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
try self.finish(context);
}
fn print_value(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")) {
std.debug.print("{s}[\n", .{indent});
for (value.items) |item| {
self.print_value(item, indent ++ " ");
self.printValue(item, indent ++ " ");
}
std.debug.print("{s}]\n", .{indent});
} else {
@ -250,11 +295,11 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (!forced_ordinal and arg.len > 1 and arg[0] == '-') {
if (arg.len > 2 and arg[1] == '-') {
try self.parse_long_tag(name, arg, &argit);
try self.parseLongTag(name, arg, &argit);
continue :argloop;
} else if (arg.len > 1) {
for (arg[1..], 1..) |short, idx| {
try self.parse_short_tag(name, short, arg.len - idx - 1, &argit);
try self.parseShortTag(name, short, arg.len - idx - 1, &argit);
}
continue :argloop;
}
@ -264,7 +309,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
forced_ordinal = true;
}
if (try self.parse_ordinals(arg, &argit)) |verb| {
if (try self.parseOrdinals(arg, &argit)) |verb| {
self.subcommand = verb;
// TODO: return slice of remaining or offset index
return argit.index;
@ -274,7 +319,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
return 0;
}
fn parse_long_tag(
fn parseLongTag(
self: *@This(),
name: []const u8,
arg: []const u8,
@ -282,7 +327,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
) ParseError!void {
if (comptime command.help_flag.long_tag) |long|
if (std.mem.eql(u8, arg, long))
self.print_help(name);
self.printHelp(name);
inline for (comptime parameters) |param| {
const PType = @TypeOf(param);
@ -292,9 +337,9 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (std.mem.startsWith(u8, arg, tag)) match: {
if (arg.len == tag.len) {
try self.apply_param_values(param, argit, false);
try self.applyParamValues(param, argit, false);
} else if (arg[tag.len] == '=') {
try self.apply_fused_values(param, arg[tag.len + 1 ..]);
try self.applyFusedValues(param, arg[tag.len + 1 ..]);
} else break :match;
return;
@ -304,7 +349,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
return ParseError.UnknownLongTagParameter;
}
fn parse_short_tag(
fn parseShortTag(
self: *@This(),
name: []const u8,
arg: u8,
@ -313,7 +358,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
) ParseError!void {
if (comptime command.help_flag.short_tag) |short|
if (arg == short[1])
self.print_help(name);
self.printHelp(name);
inline for (comptime parameters) |param| {
const PType = @TypeOf(param);
@ -326,7 +371,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (remaining > 0)
return ParseError.FusedShortTagValueMissing;
try self.apply_param_values(param, argit, false);
try self.applyParamValues(param, argit, false);
return;
}
}
@ -334,7 +379,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
return ParseError.UnknownShortTagParameter;
}
fn parse_ordinals(
fn parseOrdinals(
self: *@This(),
arg: []const u8,
argit: *ncmeta.SliceIterator([][:0]u8),
@ -346,9 +391,9 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (self.consumed_args == arg_index) {
argit.rewind();
if (comptime @TypeOf(param).G.multi) {
while (argit.peek()) |_| try self.apply_param_values(param, argit, false);
while (argit.peek()) |_| try self.applyParamValues(param, argit, false);
} else {
try self.apply_param_values(param, argit, false);
try self.applyParamValues(param, argit, false);
}
self.consumed_args += 1;
return null;
@ -360,7 +405,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
return self.subcommands.get(arg) orelse ParseError.ExtraValue;
}
fn push_intermediate_value(
fn pushIntermediateValue(
self: *@This(),
comptime param: anytype,
// @TypeOf(param).G.IntermediateValue() should work but appears to trigger a
@ -381,18 +426,18 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
}
}
fn apply_param_values(
fn applyParamValues(
self: *@This(),
comptime param: anytype,
argit: anytype,
bounded: bool,
) ParseError!void {
switch (comptime @TypeOf(param).G.value_count) {
.flag => try self.push_intermediate_value(param, comptime param.flag_bias.string()),
.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.push_intermediate_value(param, argit.next() orelse return ParseError.MissingValue),
1 => try self.pushIntermediateValue(param, argit.next() orelse return ParseError.MissingValue),
else => |total| {
var list = std.ArrayList([]const u8).initCapacity(self.allocator, total) catch
return ParseError.UnexpectedFailure;
@ -404,39 +449,39 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
}
if (bounded and argit.next() != null) return ParseError.ExtraValue;
try self.push_intermediate_value(param, list);
try self.pushIntermediateValue(param, list);
},
},
}
}
fn apply_fused_values(
fn applyFusedValues(
self: *@This(),
comptime param: anytype,
value: []const u8,
) ParseError!void {
var iter = std.mem.split(u8, value, ",");
return try self.apply_param_values(param, &iter, true);
return try self.applyParamValues(param, &iter, true);
}
fn read_environment(self: *@This(), env: std.process.EnvMap) !void {
fn readEnvironment(self: *@This(), env: std.process.EnvMap) !void {
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;
if (comptime @TypeOf(param).G.value_count == .flag) {
try self.push_intermediate_value(param, val);
try self.pushIntermediateValue(param, val);
} else {
try self.apply_fused_values(param, val);
try self.applyFusedValues(param, val);
}
}
}
}
fn convert_eager(self: *@This(), context: UserContext) NoclipError!void {
fn convertEager(self: *@This(), context: UserContext) NoclipError!void {
inline for (comptime parameters) |param| {
if (comptime param.eager) {
try self.convert_param(param, context);
try self.convertParam(param, context);
}
}
}
@ -444,12 +489,12 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
fn convert(self: *@This(), context: UserContext) NoclipError!void {
inline for (comptime parameters) |param| {
if (comptime !param.eager) {
try self.convert_param(param, context);
try self.convertParam(param, context);
}
}
}
fn convert_param(self: *@This(), comptime param: anytype, context: UserContext) NoclipError!void {
fn convertParam(self: *@This(), comptime param: anytype, context: UserContext) NoclipError!void {
if (@field(self.intermediate, param.name)) |intermediate| {
var buffer = std.ArrayList(u8).init(self.allocator);
const writer = buffer.writer();
@ -484,11 +529,11 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
}
}
fn print_help(self: *@This(), name: []const u8) noreturn {
fn printHelp(self: *@This(), name: []const u8) noreturn {
defer std.process.exit(0);
const stderr = std.io.getStdErr().writer();
if (self.help_builder.build_message(name, self.subcommands)) |message|
if (self.help_builder.buildMessage(name, self.subcommands)) |message|
stderr.writeAll(message) catch return
else |_|
stderr.writeAll("There was a problem generating the help.") catch return;