242 lines
7.2 KiB
Zig
242 lines
7.2 KiB
Zig
pub const CommandOptions = struct {
|
|
context_type: type = void,
|
|
|
|
default_help_flags: bool = true,
|
|
create_help_command: enum { always, never, if_subcommands } = .if_subcommands,
|
|
create_completion_helper: bool = true,
|
|
allow_colored_output: bool = true,
|
|
output_strategy: enum { type, iterator } = .type,
|
|
parse_error_behavior: enum { exit, propagate } = .propagate,
|
|
// pop the callback stack after parsing all arguments for the current subcommand
|
|
pipeline_subcommands: bool = false,
|
|
};
|
|
|
|
const __Canary = opaque {};
|
|
|
|
pub const ErrorReport = struct {};
|
|
|
|
pub fn Status(comptime T: type) type {
|
|
return union(enum) {
|
|
success: T,
|
|
failure: ErrorReport,
|
|
};
|
|
}
|
|
|
|
pub const String = struct {
|
|
bytes: []const u8,
|
|
};
|
|
|
|
pub const Codepoint = u21;
|
|
|
|
pub const ParameterType = enum {
|
|
flag,
|
|
// counter
|
|
// fixed_value
|
|
// aggregate_flag
|
|
option,
|
|
// aggregate_option
|
|
argument,
|
|
// aggregate_argument
|
|
// group,
|
|
};
|
|
|
|
pub const Scope = enum { local, global };
|
|
|
|
pub const MultiMode = enum {
|
|
first,
|
|
last,
|
|
accumulate,
|
|
count,
|
|
};
|
|
|
|
pub fn Accumulate(comptime T: type) type {
|
|
return struct {
|
|
const __noclip_canary__ = __Canary;
|
|
|
|
pub const Result: type = T;
|
|
pub const multi_mode: MultiMode = .accumulate;
|
|
};
|
|
}
|
|
|
|
pub fn Count(comptime T: type) type {
|
|
if (!@typeInfo(T) == .int) unreachable;
|
|
return struct {
|
|
const __noclip_canary__ = __Canary;
|
|
|
|
pub const Result: type = T;
|
|
pub const multi_mode: MultiMode = .count;
|
|
};
|
|
}
|
|
|
|
pub const FlagSet = struct {
|
|
pub const Result = bool;
|
|
pub const param_type: ParameterType = .flag;
|
|
|
|
description: []const u8 = "",
|
|
truthy: Pair = .{},
|
|
falsy: Pair = .{},
|
|
env: ?[]const u8 = null,
|
|
/// If true, at least one of the variants of the flag must be provided by
|
|
/// the user on the command line, otherwise a parse error will be produced.
|
|
required: bool = false,
|
|
/// A default value that will be forwarded if the option is not provided on
|
|
/// the command line by the user. If a default is provided, then the
|
|
/// corresponding parsed value will not be optional. Note that flags are
|
|
/// tri-state values that may be `null`, `true`, or `false`. `null` will
|
|
/// never be forwarded if this is set to `true` or `false`, as `null` only
|
|
/// indicates that the flag was not specified on the command line.
|
|
default: ?Result = null,
|
|
// multi: Multi = .last,
|
|
scope: Scope = .local,
|
|
eager: bool = false,
|
|
hidden: bool = false,
|
|
|
|
pub const Pair = struct {
|
|
/// a single unicode codepoint that identifies this flag on the command
|
|
/// line, e.g. 'v'.
|
|
short: ?Codepoint = null,
|
|
/// a string, beginning with the long flag sequence `--` that identifies
|
|
/// this flag on the command line, e.g. "--version". Multiple words
|
|
/// should be skewercase, i.e. "--multiple-words".
|
|
long: ?[]const u8 = null,
|
|
};
|
|
};
|
|
|
|
pub const Counter = struct {
|
|
pub const Result = u64;
|
|
pub const param_type: ParameterType = .flag;
|
|
|
|
description: []const u8 = "",
|
|
short: ?Codepoint = null,
|
|
long: ?[]const u8 = null,
|
|
required: bool = false,
|
|
scope: Scope = .local,
|
|
hidden: bool = false,
|
|
};
|
|
|
|
pub fn Group(comptime R: type) type {
|
|
return struct {
|
|
pub const Result = R;
|
|
// pub const param_type: ParameterType = .group;
|
|
|
|
// description: []const u8 = "",
|
|
// env: ?[]const u8 = null,
|
|
/// at least one of the parameters in the group must be provided
|
|
required: bool = false,
|
|
parameters: type,
|
|
|
|
pub fn validate(self: @This()) Status(void) {
|
|
comptime {
|
|
for (@typeInfo(@TypeOf(self.parameters)).Struct.decls) |td| {
|
|
const decl = @field(@TypeOf(self.parameters), td.name);
|
|
std.debug.assert(decl.Result == Result);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// figure this out: this is a zero-parameter flag that produces a non-boolean
|
|
// value, e.g. an int. for like -9 on gz. A flag is just a FixedValue with
|
|
pub fn FixedValue(comptime R: type) type {
|
|
return struct {
|
|
pub const Result = R;
|
|
pub const param_type: ParameterType = .flag;
|
|
|
|
description: []const u8 = "",
|
|
short: ?Codepoint = null,
|
|
long: ?[]const u8 = null,
|
|
env: ?[]const u8 = null,
|
|
/// Require that the user always provide a value for this option on the
|
|
/// command line.
|
|
required: bool = false,
|
|
/// A default value that will be forwarded if the option is not provided
|
|
/// on the command line by the user. If a default is provided, then the
|
|
/// corresponding parsed value will not be optional.
|
|
value: Result,
|
|
scope: Scope = .local,
|
|
eager: bool = false,
|
|
hidden: bool = false,
|
|
};
|
|
}
|
|
|
|
pub fn Option(comptime R: type) type {
|
|
return struct {
|
|
pub const Result = R;
|
|
pub const param_type: ParameterType = .option;
|
|
|
|
description: []const u8 = "",
|
|
short: ?Codepoint = null,
|
|
long: ?[]const u8 = null,
|
|
env: ?[]const u8 = null,
|
|
/// Require that the user always provide a value for this option on the
|
|
/// command line.
|
|
required: bool = false,
|
|
/// A default value that will be forwarded if the option is not provided
|
|
/// on the command line by the user. If a default is provided, then the
|
|
/// corresponding parsed value will not be optional.
|
|
default: ?Result = null,
|
|
/// note: .count is only valid for flags
|
|
/// note: .accumulate requires R to be a slice
|
|
// multi: Multi = .last,
|
|
scope: Scope = .local,
|
|
eager: bool = false,
|
|
hidden: bool = false,
|
|
};
|
|
}
|
|
|
|
pub fn Argument(comptime R: type) type {
|
|
return struct {
|
|
pub const Result = R;
|
|
|
|
description: []const u8 = "",
|
|
// note: equivalent to .accumulate. Requires R to be a slice
|
|
multi: bool = false,
|
|
};
|
|
}
|
|
|
|
pub fn execute(comptime spec: type, args: ExecArgs(spec)) ReturnType(spec) {
|
|
var parser = Parser(spec).init(args.alloc, args.context);
|
|
defer parser.deinit();
|
|
switch (parser.parse(args.args, args.env)) {
|
|
.success => |callstack| {
|
|
for (callstack) |runner| {
|
|
try runner();
|
|
}
|
|
},
|
|
.fail => |report| {
|
|
switch (spec.info.options.parse_error_behavior) {
|
|
.exit => {},
|
|
.propagate => {
|
|
if (@hasField(spec, "err"))
|
|
spec.err(report);
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn ExecArgs(comptime spec: type) type {
|
|
return struct {
|
|
alloc: std.mem.Allocator,
|
|
args: []const [:0]const u8,
|
|
env: std.process.EnvMap,
|
|
context: spec.info.context_type,
|
|
};
|
|
}
|
|
|
|
pub fn ReturnType(comptime spec: type) type {
|
|
const info = @typeInfo(@TypeOf(spec.run)).Fn;
|
|
return switch (@typeInfo(info.return_type.?)) {
|
|
.ErrorUnion => |eu| blk: {
|
|
if (eu.payload != void) unreachable;
|
|
break :blk info.return_type.?;
|
|
},
|
|
.Void => void,
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
pub const Parser = @import("./parser.zig");
|
|
const std = @import("std");
|