695 lines
25 KiB
Zig
695 lines
25 KiB
Zig
|
const std = @import("std");
|
||
|
const StructField = std.builtin.Type.StructField;
|
||
|
|
||
|
const converters = @import("./converters.zig");
|
||
|
const ncmeta = @import("./meta.zig");
|
||
|
|
||
|
const ConverterSignature = converters.ConverterSignature;
|
||
|
|
||
|
const ParameterType = enum {
|
||
|
Nominal,
|
||
|
Ordinal,
|
||
|
Executable,
|
||
|
};
|
||
|
|
||
|
const Errors = error{
|
||
|
BadConfiguration,
|
||
|
MissingTag,
|
||
|
ArgumentWithTags,
|
||
|
ArgumentWithEnvVar,
|
||
|
MissingDefaultConverter,
|
||
|
};
|
||
|
|
||
|
const ParseError = error{
|
||
|
ValueMissing,
|
||
|
FusedShortTagValueMissing,
|
||
|
UnknownLongTagParameter,
|
||
|
UnknownShortTagParameter,
|
||
|
};
|
||
|
|
||
|
const FlagBias = enum {
|
||
|
falsy,
|
||
|
truthy,
|
||
|
unbiased,
|
||
|
|
||
|
pub fn string(self: @This()) []const u8 {
|
||
|
return switch (self) {
|
||
|
.truthy => "true",
|
||
|
else => @compileLog(self),
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const OptionResult = union(enum) {
|
||
|
Value: type,
|
||
|
flag: FlagBias,
|
||
|
};
|
||
|
|
||
|
pub const ParameterGenerics = struct {
|
||
|
ContextType: type = void,
|
||
|
result: OptionResult = .{ .Value = []const u8 },
|
||
|
param_type: ParameterType,
|
||
|
|
||
|
pub fn no_context(comptime self: @This()) bool {
|
||
|
return self.ContextType == void;
|
||
|
}
|
||
|
|
||
|
pub fn is_flag(comptime self: @This()) bool {
|
||
|
return self.result == .flag;
|
||
|
}
|
||
|
|
||
|
pub fn clone(comptime self: @This(), comptime NewResult: type) @This() {
|
||
|
return @This(){
|
||
|
.ContextType = self.ContextType,
|
||
|
.result = .{ .Value = NewResult },
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn ResultType(comptime self: @This()) type {
|
||
|
return switch (self.result) {
|
||
|
.Value => |res| res,
|
||
|
.flag => bool,
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const ValuedGenericsBasis = struct { ContextType: type = void, Result: type };
|
||
|
const FlagGenericsBasis = struct { ContextType: type = void, flag_bias: FlagBias = .truthy };
|
||
|
|
||
|
fn tag_generics(comptime basis: ValuedGenericsBasis) ParameterGenerics {
|
||
|
return ParameterGenerics{
|
||
|
.ContextType = basis.ContextType,
|
||
|
.result = .{ .Value = basis.Result },
|
||
|
.param_type = .Nominal,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn flag_generics(comptime basis: FlagGenericsBasis) ParameterGenerics {
|
||
|
return ParameterGenerics{
|
||
|
.ContextType = basis.ContextType,
|
||
|
.result = .{ .flag = basis.flag_bias },
|
||
|
.param_type = .Nominal,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn arg_generics(comptime basis: ValuedGenericsBasis) ParameterGenerics {
|
||
|
return ParameterGenerics{
|
||
|
.ContextType = basis.ContextType,
|
||
|
.result = .{ .Value = basis.Result },
|
||
|
.param_type = .Ordinal,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn OptionConfig(comptime generics: ParameterGenerics) type {
|
||
|
return struct {
|
||
|
name: []const u8,
|
||
|
|
||
|
short_tag: ?[]const u8 = null,
|
||
|
long_tag: ?[]const u8 = null,
|
||
|
env_var: ?[]const u8 = null,
|
||
|
|
||
|
default: ?generics.ResultType() = null,
|
||
|
converter: ?ConverterSignature(generics) = null,
|
||
|
arg_count: u32 = if (generics.is_flag()) 0 else 1,
|
||
|
eager: bool = false,
|
||
|
required: bool = generics.param_type == .Ordinal,
|
||
|
exposed: bool = true,
|
||
|
secret: bool = false,
|
||
|
nice_type_name: []const u8 = @typeName(generics.ResultType()),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn OptionType(comptime generics: ParameterGenerics) type {
|
||
|
return struct {
|
||
|
pub const gen = generics;
|
||
|
pub const param_type: ParameterType = generics.param_type;
|
||
|
pub const is_flag: bool = generics.is_flag();
|
||
|
pub const flag_bias: FlagBias = if (generics.is_flag()) generics.result.flag else .unbiased;
|
||
|
|
||
|
name: []const u8,
|
||
|
short_tag: ?[]const u8,
|
||
|
long_tag: ?[]const u8,
|
||
|
env_var: ?[]const u8,
|
||
|
|
||
|
default: ?generics.ResultType(),
|
||
|
converter: ConverterSignature(generics),
|
||
|
description: []const u8 = "", // description for output in help text
|
||
|
arg_count: u32,
|
||
|
eager: bool,
|
||
|
required: bool,
|
||
|
exposed: bool, // do not expose the resulting value in the output type. the handler must have side effects for this option to do anything
|
||
|
secret: bool, // do not print help for this parameter
|
||
|
nice_type_name: ?[]const u8 = null, // friendly type name (string better than []const u8)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn check_short(comptime short_tag: ?[]const u8) void {
|
||
|
if (short_tag) |short| {
|
||
|
if (short.len != 2 or short[0] != '-') @compileError("bad short tag");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_long(comptime long_tag: ?[]const u8) void {
|
||
|
if (long_tag) |long| {
|
||
|
if (long.len < 3 or long[0] != '-' or long[1] != '-') @compileError("bad long tag");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn make_option(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 " ++
|
||
|
opts.name ++
|
||
|
" must have at least one of a short tag, a long tag, or an environment variable",
|
||
|
);
|
||
|
}
|
||
|
|
||
|
check_short(opts.short_tag);
|
||
|
check_long(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
|
||
|
// may be an error. That's the essential distinction between the OptionType
|
||
|
// and the OptionConfig, is the OptionConfig is just unvalidated parameters,
|
||
|
// whereas the OptionType is an instance of an object that has been
|
||
|
// validated.
|
||
|
const converter = opts.converter orelse converters.default_converter(generics) orelse {
|
||
|
@compileLog(opts);
|
||
|
@compileError("implement me");
|
||
|
};
|
||
|
|
||
|
return OptionType(generics){
|
||
|
.name = opts.name,
|
||
|
.short_tag = opts.short_tag,
|
||
|
.long_tag = opts.long_tag,
|
||
|
.env_var = opts.env_var,
|
||
|
.default = opts.default,
|
||
|
.converter = converter,
|
||
|
.arg_count = opts.arg_count,
|
||
|
.eager = opts.eager,
|
||
|
.required = opts.required,
|
||
|
.exposed = opts.exposed,
|
||
|
.secret = opts.secret,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn make_argument(comptime generics: ParameterGenerics, comptime opts: OptionConfig(generics)) OptionType(generics) {
|
||
|
// TODO: it would technically be possible to support specification of
|
||
|
// ordered arguments through environmental variables, but it doesn't really
|
||
|
// make a lot of sense. The algorithm would consume the env var greedily
|
||
|
if (opts.short_tag != null or opts.long_tag != null or opts.env_var != null) {
|
||
|
@compileLog(opts);
|
||
|
@compileError("argument " ++ opts.name ++ " must not have a long or short tag or an env var");
|
||
|
}
|
||
|
|
||
|
const converter = opts.converter orelse converters.default_converter(generics) orelse {
|
||
|
@compileLog(opts);
|
||
|
@compileError("implement me");
|
||
|
};
|
||
|
|
||
|
return OptionType(generics){
|
||
|
.name = opts.name,
|
||
|
.short_tag = opts.short_tag,
|
||
|
.long_tag = opts.long_tag,
|
||
|
.env_var = opts.env_var,
|
||
|
.default = opts.default,
|
||
|
.converter = converter,
|
||
|
.arg_count = opts.arg_count,
|
||
|
.eager = opts.eager,
|
||
|
.required = opts.required,
|
||
|
.exposed = opts.exposed,
|
||
|
.secret = opts.secret,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const ShortLongPair = struct {
|
||
|
short_tag: ?[]const u8 = null,
|
||
|
long_tag: ?[]const u8 = null,
|
||
|
};
|
||
|
|
||
|
fn FlagBuilderArgs(comptime ContextType: type) type {
|
||
|
return struct {
|
||
|
name: []const u8,
|
||
|
truthy: ?ShortLongPair = null,
|
||
|
falsy: ?ShortLongPair = null,
|
||
|
env_var: ?[]const u8 = null,
|
||
|
|
||
|
default: ?bool = null,
|
||
|
converter: ?ConverterSignature(flag_generics(.{ .ContextType = ContextType })) = null,
|
||
|
eager: bool = false,
|
||
|
exposed: bool = true,
|
||
|
required: bool = false,
|
||
|
secret: bool = false,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn CommandBuilder(comptime ContextType: type) type {
|
||
|
return struct {
|
||
|
param_spec: ncmeta.MutableTuple = .{},
|
||
|
|
||
|
pub const UserContextType = ContextType;
|
||
|
|
||
|
pub fn add_argument(
|
||
|
comptime self: *@This(),
|
||
|
comptime Result: type,
|
||
|
comptime args: OptionConfig(arg_generics(.{ .ContextType = ContextType, .Result = Result })),
|
||
|
) void {
|
||
|
self.param_spec.add(make_argument(
|
||
|
arg_generics(.{ .ContextType = ContextType, .Result = Result }),
|
||
|
args,
|
||
|
));
|
||
|
}
|
||
|
|
||
|
pub fn add_option(
|
||
|
comptime self: *@This(),
|
||
|
comptime Result: type,
|
||
|
comptime args: OptionConfig(tag_generics(.{ .ContextType = ContextType, .Result = Result })),
|
||
|
) void {
|
||
|
self.param_spec.add(make_option(
|
||
|
tag_generics(.{ .ContextType = ContextType, .Result = Result }),
|
||
|
args,
|
||
|
));
|
||
|
}
|
||
|
|
||
|
pub fn add_flag(
|
||
|
comptime self: *@This(),
|
||
|
comptime build_args: FlagBuilderArgs(ContextType),
|
||
|
) void {
|
||
|
if (build_args.truthy == null and build_args.falsy == null and build_args.env_var == null) {
|
||
|
@compileError(
|
||
|
"flag " ++
|
||
|
build_args.name ++
|
||
|
" must have at least one of truthy flags, falsy flags, or env_var flags",
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (build_args.truthy) |truthy_pair| {
|
||
|
if (truthy_pair.short_tag == null and truthy_pair.long_tag == null) {
|
||
|
@compileError(
|
||
|
"flag " ++
|
||
|
build_args.name ++
|
||
|
" truthy pair must have at least short or long tags set",
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const generics = flag_generics(.{ .ContextType = ContextType, .flag_bias = .truthy });
|
||
|
|
||
|
const args = OptionConfig(generics){
|
||
|
.name = build_args.name,
|
||
|
.short_tag = truthy_pair.short_tag,
|
||
|
.long_tag = truthy_pair.long_tag,
|
||
|
.env_var = null,
|
||
|
.default = build_args.default,
|
||
|
.converter = build_args.converter,
|
||
|
.eager = build_args.eager,
|
||
|
.exposed = build_args.exposed,
|
||
|
.secret = build_args.secret,
|
||
|
};
|
||
|
|
||
|
self.param_spec.add(make_option(generics, args));
|
||
|
}
|
||
|
|
||
|
if (build_args.falsy) |falsy_pair| {
|
||
|
if (falsy_pair.short_tag == null and falsy_pair.long_tag == null) {
|
||
|
@compileError(
|
||
|
"flag " ++
|
||
|
build_args.name ++
|
||
|
" falsy pair must have at least short or long tags set",
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const generics = flag_generics(.{ .ContextType = ContextType, .flag_bias = .falsy });
|
||
|
const args = OptionConfig(generics){
|
||
|
.name = build_args.name,
|
||
|
.short_tag = falsy_pair.short_tag,
|
||
|
.long_tag = falsy_pair.long_tag,
|
||
|
.env_var = null,
|
||
|
.default = build_args.default,
|
||
|
.converter = build_args.converter,
|
||
|
.eager = build_args.eager,
|
||
|
.secret = build_args.secret,
|
||
|
};
|
||
|
|
||
|
self.param_spec.add(make_option(generics, args));
|
||
|
}
|
||
|
|
||
|
if (build_args.env_var) |env_var| {
|
||
|
const generics = flag_generics(.{ .ContextType = ContextType, .flag_bias = .unbiased });
|
||
|
const args = OptionConfig(generics){
|
||
|
.name = build_args.name,
|
||
|
.env_var = env_var,
|
||
|
.default = build_args.default,
|
||
|
.converter = build_args.converter,
|
||
|
.eager = build_args.eager,
|
||
|
.secret = build_args.secret,
|
||
|
};
|
||
|
|
||
|
self.param_spec.add(make_option(generics, args));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn generate(comptime self: @This()) self.param_spec.TupleType() {
|
||
|
return self.param_spec.realTuple();
|
||
|
}
|
||
|
|
||
|
pub fn CallbackSignature(comptime self: @This()) type {
|
||
|
return *const fn (ContextType, self.CommandOutput()) anyerror!void;
|
||
|
}
|
||
|
|
||
|
pub fn CommandOutput(comptime self: @This()) type {
|
||
|
comptime {
|
||
|
const spec = self.generate();
|
||
|
var fields: []const StructField = &[0]StructField{};
|
||
|
var flag_skip = 0;
|
||
|
|
||
|
paramloop: for (spec, 0..) |param, idx| {
|
||
|
if (!param.exposed) continue :paramloop;
|
||
|
while (flag_skip > 0) {
|
||
|
flag_skip -= 1;
|
||
|
continue :paramloop;
|
||
|
}
|
||
|
|
||
|
const PType = @TypeOf(param);
|
||
|
if (PType.is_flag) {
|
||
|
var peek = idx + 1;
|
||
|
var bais_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias);
|
||
|
bais_seen[@enumToInt(PType.flag_bias)] = true;
|
||
|
while (peek < spec.len) : (peek += 1) {
|
||
|
const peek_param = spec[peek];
|
||
|
const PeekType = @TypeOf(peek_param);
|
||
|
|
||
|
if (PeekType.is_flag and std.mem.eql(u8, param.name, peek_param.name)) {
|
||
|
if (bais_seen[@enumToInt(PeekType.flag_bias)] == true) {
|
||
|
@compileError("redundant flag!!!! " ++ param.name ++ " and " ++ peek_param.name);
|
||
|
} else {
|
||
|
bais_seen[@enumToInt(PeekType.flag_bias)] = true;
|
||
|
}
|
||
|
flag_skip += 1;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the default field is already the optional type. Stripping
|
||
|
// the optional wrapper is an interesting idea for required
|
||
|
// fields. I do not foresee this greatly increasing complexity here.
|
||
|
const FieldType = if (param.required)
|
||
|
std.meta.Child(std.meta.FieldType(PType, .default))
|
||
|
else
|
||
|
std.meta.FieldType(PType, .default);
|
||
|
|
||
|
// the wacky comptime slice extension hack
|
||
|
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
|
||
|
.name = param.name,
|
||
|
.type = FieldType,
|
||
|
.default_value = @ptrCast(?*const anyopaque, ¶m.default),
|
||
|
.is_comptime = false,
|
||
|
.alignment = @alignOf(FieldType),
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
return @Type(.{ .Struct = .{
|
||
|
.layout = .Auto,
|
||
|
.fields = fields,
|
||
|
.decls = &.{},
|
||
|
.is_tuple = false,
|
||
|
} });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn Intermediate(comptime self: @This()) type {
|
||
|
comptime {
|
||
|
const spec = self.generate();
|
||
|
var fields: []const StructField = &[0]StructField{};
|
||
|
var flag_skip = 0;
|
||
|
|
||
|
paramloop: for (spec, 0..) |param, idx| {
|
||
|
while (flag_skip > 0) {
|
||
|
flag_skip -= 1;
|
||
|
continue :paramloop;
|
||
|
}
|
||
|
|
||
|
const PType = @TypeOf(param);
|
||
|
if (PType.is_flag) {
|
||
|
var peek = idx + 1;
|
||
|
var bais_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias);
|
||
|
bais_seen[@enumToInt(PType.flag_bias)] = true;
|
||
|
while (peek < spec.len) : (peek += 1) {
|
||
|
const peek_param = spec[peek];
|
||
|
const PeekType = @TypeOf(peek_param);
|
||
|
|
||
|
if (PeekType.is_flag and std.mem.eql(u8, param.name, peek_param.name)) {
|
||
|
if (bais_seen[@enumToInt(PeekType.flag_bias)] == true) {
|
||
|
@compileError("redundant flag!!!! " ++ param.name ++ " and " ++ peek_param.name);
|
||
|
} else {
|
||
|
bais_seen[@enumToInt(PeekType.flag_bias)] = true;
|
||
|
}
|
||
|
flag_skip += 1;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the wacky comptime slice extension hack
|
||
|
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
|
||
|
.name = param.name,
|
||
|
.type = ?[]const u8,
|
||
|
.default_value = @ptrCast(?*const anyopaque, &@as(?[]const u8, null)),
|
||
|
.is_comptime = false,
|
||
|
.alignment = @alignOf(?[]const u8),
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
return @Type(.{ .Struct = .{
|
||
|
.layout = .Auto,
|
||
|
.fields = fields,
|
||
|
.decls = &.{},
|
||
|
.is_tuple = false,
|
||
|
} });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn bind(comptime self: @This(), comptime callback: self.CallbackSignature()) Parser(self, callback) {
|
||
|
return Parser(self, callback){};
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// the parser is generated by the bind method of the CommandBuilder, so we can
|
||
|
// be extremely type-sloppy here, which simplifies the signature.
|
||
|
fn Parser(comptime command: anytype, comptime callback: anytype) type {
|
||
|
_ = callback;
|
||
|
|
||
|
return struct {
|
||
|
const ContextType = @TypeOf(command).UserContextType;
|
||
|
// let there be fields! we can move some things to runtime.
|
||
|
// We can get some better behavior if we defer converting non-eager
|
||
|
// options until the entire command line has been parsed. However,
|
||
|
// to do that, we effectively have to store the parameters as strings until the
|
||
|
// entire line has been parsed.
|
||
|
|
||
|
// a goal is to
|
||
|
|
||
|
intermediate: command.Intermediate() = .{},
|
||
|
consumed_args: u32 = 0,
|
||
|
|
||
|
// pub fn add_subcommand(self: *@This(), verb: []const u8, parser: anytype) void {
|
||
|
// self.subcommands
|
||
|
// }
|
||
|
|
||
|
pub fn parse(
|
||
|
self: *@This(),
|
||
|
alloc: std.mem.Allocator,
|
||
|
argit: *std.process.ArgIterator,
|
||
|
env: std.process.EnvMap,
|
||
|
context: ContextType,
|
||
|
) anyerror!void {
|
||
|
_ = alloc;
|
||
|
// _ = context;
|
||
|
|
||
|
try self.read_environment(env);
|
||
|
|
||
|
var forced_args = false;
|
||
|
argloop: while (argit.next()) |arg| {
|
||
|
if (!forced_args and std.mem.eql(u8, arg, "--")) {
|
||
|
forced_args = true;
|
||
|
continue :argloop;
|
||
|
}
|
||
|
|
||
|
parse_tags: {
|
||
|
if (forced_args or arg.len < 1 or arg[0] != '-') break :parse_tags;
|
||
|
|
||
|
if (arg.len > 2 and arg[1] == '-') {
|
||
|
try self.parse_long_tag(arg, argit, context);
|
||
|
continue :argloop;
|
||
|
} else if (arg.len > 1) {
|
||
|
for (arg[1..], 1..) |short, idx| {
|
||
|
// _ = short;
|
||
|
// _ = idx;
|
||
|
try self.parse_short_tag(short, arg.len - idx - 1, argit, context);
|
||
|
}
|
||
|
continue :argloop;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try self.parse_argument(arg, argit);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline fn parse_long_tag(
|
||
|
self: *@This(),
|
||
|
arg: []const u8,
|
||
|
argit: *std.process.ArgIterator,
|
||
|
context: ContextType,
|
||
|
) ParseError!void {
|
||
|
_ = context;
|
||
|
|
||
|
inline for (comptime command.generate()) |param| {
|
||
|
const PType = @TypeOf(param);
|
||
|
// removing the comptime here causes the compiler to die
|
||
|
comptime if (PType.param_type != .Nominal or param.long_tag == null) continue;
|
||
|
const tag = param.long_tag.?;
|
||
|
|
||
|
if (comptime PType.is_flag) {
|
||
|
if (std.mem.eql(u8, arg, tag)) {
|
||
|
@field(self.intermediate, param.name) = if (comptime PType.flag_bias == .truthy) "true" else "false";
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if (std.mem.startsWith(u8, arg, tag)) match: {
|
||
|
// TODO: handle more than one value
|
||
|
const next = if (arg.len == tag.len)
|
||
|
argit.next() orelse return ParseError.ValueMissing
|
||
|
else if (arg[tag.len] == '=')
|
||
|
arg[tag.len + 1 ..]
|
||
|
else
|
||
|
break :match;
|
||
|
|
||
|
@field(self.intermediate, param.name) = next;
|
||
|
// if (comptime param.eager) {
|
||
|
// try param.converter()
|
||
|
// }
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ParseError.UnknownLongTagParameter;
|
||
|
}
|
||
|
|
||
|
inline fn parse_short_tag(
|
||
|
self: *@This(),
|
||
|
arg: u8,
|
||
|
remaining: usize,
|
||
|
argit: *std.process.ArgIterator,
|
||
|
context: ContextType,
|
||
|
) ParseError!void {
|
||
|
_ = context;
|
||
|
|
||
|
inline for (comptime command.generate()) |param| {
|
||
|
const PType = @TypeOf(param);
|
||
|
// removing the comptime here causes the compiler to die
|
||
|
comptime if (PType.param_type != .Nominal or param.short_tag == null) continue;
|
||
|
const tag = param.short_tag.?;
|
||
|
|
||
|
if (comptime PType.is_flag) {
|
||
|
if (arg == tag[1]) {
|
||
|
@field(self.intermediate, param.name) = if (comptime PType.flag_bias == .truthy) "true" else "false";
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if (arg == tag[1]) {
|
||
|
if (remaining > 0) return ParseError.FusedShortTagValueMissing;
|
||
|
const next = argit.next() orelse return ParseError.ValueMissing;
|
||
|
|
||
|
@field(self.intermediate, param.name) = next;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ParseError.UnknownShortTagParameter;
|
||
|
}
|
||
|
|
||
|
inline fn parse_argument(self: *@This(), arg: []const u8, argit: *std.process.ArgIterator) ParseError!void {
|
||
|
_ = argit;
|
||
|
|
||
|
comptime var arg_index: u32 = 0;
|
||
|
inline for (comptime command.generate()) |param| {
|
||
|
if (@TypeOf(param).param_type != .Ordinal) continue;
|
||
|
|
||
|
if (self.consumed_args == arg_index) {
|
||
|
std.debug.print("n: {s}, c: {d}, i: {d}\n", .{ param.name, self.consumed_args, arg_index });
|
||
|
@field(self.intermediate, param.name) = arg;
|
||
|
self.consumed_args += 1;
|
||
|
return;
|
||
|
}
|
||
|
arg_index += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn read_environment(self: *@This(), env: std.process.EnvMap) !void {
|
||
|
inline for (comptime command.generate()) |param| {
|
||
|
if (comptime param.env_var) |env_var| {
|
||
|
@field(self.intermediate, param.name) = env.get(env_var);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn HelpBuilder(comptime command: anytype) type {
|
||
|
_ = command;
|
||
|
}
|
||
|
|
||
|
pub fn command_builder(comptime ContextType: type) CommandBuilder(ContextType) {
|
||
|
return CommandBuilder(ContextType){};
|
||
|
}
|
||
|
|
||
|
const Choice = enum { first, second };
|
||
|
|
||
|
const cli = cmd: {
|
||
|
var cmd = command_builder(void);
|
||
|
cmd.add_option(u8, .{
|
||
|
.name = "test",
|
||
|
.short_tag = "-t",
|
||
|
.long_tag = "--test",
|
||
|
.env_var = "NOCLIP_TEST",
|
||
|
});
|
||
|
cmd.add_flag(.{
|
||
|
.name = "flag",
|
||
|
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
|
||
|
.falsy = .{ .long_tag = "--no-flag" },
|
||
|
.env_var = "NOCLIP_FLAG",
|
||
|
});
|
||
|
cmd.add_argument([]const u8, .{ .name = "arg" });
|
||
|
cmd.add_argument([]const u8, .{ .name = "argtwo" });
|
||
|
|
||
|
break :cmd cmd;
|
||
|
};
|
||
|
|
||
|
fn cli_handler(_: void, result: cli.CommandOutput()) !void {
|
||
|
_ = result;
|
||
|
}
|
||
|
|
||
|
pub fn main() !void {
|
||
|
// std.debug.print("hello\n", .{});
|
||
|
var parser = cli.bind(cli_handler);
|
||
|
|
||
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||
|
defer arena.deinit();
|
||
|
|
||
|
const allocator = arena.allocator();
|
||
|
var argit = try std.process.argsWithAllocator(allocator);
|
||
|
var env = try std.process.getEnvMap(allocator);
|
||
|
|
||
|
_ = argit.next();
|
||
|
try parser.parse(allocator, &argit, env, {});
|
||
|
|
||
|
inline for (@typeInfo(@TypeOf(parser.intermediate)).Struct.fields) |field| {
|
||
|
std.debug.print("{s}: {?s}\n", .{ field.name, @field(parser.intermediate, field.name) });
|
||
|
}
|
||
|
}
|