NOCLIP/source/noclip.zig

465 lines
15 KiB
Zig
Raw Normal View History

2025-02-21 23:15:59 -07:00
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 {};
2025-03-06 00:45:36 -07:00
pub const ErrorReport = struct {
message: []const u8,
};
2025-02-21 23:15:59 -07:00
pub fn Status(comptime T: type) type {
return union(enum) {
success: T,
failure: ErrorReport,
2025-03-06 00:45:36 -07:00
pub fn succeed(arg: T) @This() {
return .{ .success = arg };
}
pub fn failFull(arg: ErrorReport) @This() {
return .{ .failure = arg };
}
pub fn fail(msg: []const u8) @This() {
return .{ .failure = .{ .message = msg } };
}
2025-02-21 23:15:59 -07:00
};
}
pub const String = struct {
bytes: []const u8,
};
pub const ParameterType = enum {
2025-03-06 00:45:36 -07:00
bool_group,
constant,
counter,
2025-02-21 23:15:59 -07:00
// counter
// fixed_value
// aggregate_flag
option,
// aggregate_option
argument,
// aggregate_argument
2025-03-06 00:45:36 -07:00
group,
2025-02-21 23:15:59 -07:00
};
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;
};
}
2025-03-14 00:17:58 -06:00
pub const OptScope = struct { opt: []const u8, scope: Scope, value: bool };
2025-02-21 23:15:59 -07:00
2025-03-14 00:17:58 -06:00
pub const BoolGroup = struct {
2025-02-21 23:15:59 -07:00
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,
2025-03-14 00:17:58 -06:00
pub const Result = bool;
pub const param_type: ParameterType = .bool_group;
pub const multi_mode: MultiMode = .last;
// accessors to easily read decls from an instance
pub fn Type(comptime _: *const BoolGroup) type {
return BoolGroup.Result;
}
pub fn mode(comptime _: *const BoolGroup) MultiMode {
return BoolGroup.multi_mode;
}
pub fn shorts(comptime self: BoolGroup) []const OptScope {
comptime {
var list: []const OptScope = &.{};
if (self.truthy.short) |short|
list = list ++ &[_]OptScope{.{ .opt = mem.encodeShort(short), .scope = self.scope, .value = false }};
if (self.falsy.short) |short|
list = list ++ &[_]OptScope{.{ .opt = mem.encodeShort(short), .scope = self.scope, .value = false }};
return list;
}
}
pub fn longs(comptime self: BoolGroup) []const OptScope {
comptime {
var list: []const OptScope = &.{};
if (self.truthy.long) |long|
list = list ++ &[_]OptScope{.{ .opt = long, .scope = self.scope, .value = false }};
if (self.falsy.long) |long|
list = list ++ &[_]OptScope{.{ .opt = long, .scope = self.scope, .value = false }};
return list;
}
}
2025-02-21 23:15:59 -07:00
pub const Pair = struct {
/// a single unicode codepoint that identifies this flag on the command
/// line, e.g. 'v'.
2025-03-14 00:17:58 -06:00
short: ?mem.Codepoint = null,
2025-02-21 23:15:59 -07:00
/// 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,
};
};
2025-03-06 00:45:36 -07:00
// 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 Constant(comptime R: type) type {
2025-02-21 23:15:59 -07:00
return struct {
2025-03-06 00:45:36 -07:00
description: []const u8 = "",
2025-03-14 00:17:58 -06:00
short: ?mem.Codepoint = null,
2025-03-06 00:45:36 -07:00
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,
/// The value associated with this flag
value: Result,
scope: Scope = .local,
eager: bool = false,
hidden: bool = false,
2025-02-21 23:15:59 -07:00
2025-03-06 00:45:36 -07:00
const Self = @This();
pub const Result = ScryResultType(R);
2025-03-14 00:17:58 -06:00
pub const param_type: ParameterType = .constant;
2025-03-06 00:45:36 -07:00
pub const multi_mode: MultiMode = scryMode(R);
// accessors to easily read decls from an instance
pub fn Type(comptime _: *const Self) type {
return Self.Result;
}
pub fn mode(comptime _: *const Self) MultiMode {
return Self.multi_mode;
}
2025-02-21 23:15:59 -07:00
2025-03-14 00:17:58 -06:00
pub fn shorts(comptime self: Self) []const OptScope {
comptime return if (self.short) |short|
&[_]OptScope{.{ .opt = mem.encodeShort(short), .scope = self.scope, .value = true }}
else
&.{};
}
pub fn longs(comptime self: Self) []const OptScope {
comptime return if (self.long) |long|
&[_]OptScope{.{ .opt = long, .scope = self.scope, .value = true }}
else
&.{};
}
};
}
pub fn Counter(comptime R: type) type {
return struct {
2025-02-21 23:15:59 -07:00
description: []const u8 = "",
2025-03-14 00:17:58 -06:00
short: ?mem.Codepoint = null,
2025-02-21 23:15:59 -07:00
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,
2025-03-06 00:45:36 -07:00
/// The value associated with this flag
increment: Result = 1,
2025-02-21 23:15:59 -07:00
scope: Scope = .local,
eager: bool = false,
hidden: bool = false,
2025-03-06 00:45:36 -07:00
const Self = @This();
pub const Result = ScryResultType(R);
2025-03-14 00:17:58 -06:00
pub const param_type: ParameterType = .counter;
2025-03-06 00:45:36 -07:00
pub const multi_mode: MultiMode = scryMode(R);
// accessors to easily read decls from an instance
pub fn Type(comptime _: *const Self) type {
return Self.Result;
}
pub fn mode(comptime _: *const Self) MultiMode {
return Self.multi_mode;
}
2025-02-21 23:15:59 -07:00
2025-03-14 00:17:58 -06:00
pub fn shorts(comptime self: Self) []const OptScope {
comptime return if (self.short) |short|
&[_]OptScope{.{ .opt = mem.encodeShort(short), .scope = self.scope, .value = true }}
else
&.{};
}
pub fn longs(comptime self: Self) []const OptScope {
comptime return if (self.long) |long|
&[_]OptScope{.{ .opt = long, .scope = self.scope, .value = true }}
else
&.{};
}
};
}
pub fn Option(comptime R: type) type {
return struct {
2025-02-21 23:15:59 -07:00
description: []const u8 = "",
2025-03-14 00:17:58 -06:00
short: ?mem.Codepoint = null,
2025-02-21 23:15:59 -07:00
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.
2025-03-06 00:45:36 -07:00
default: DefaultType(R) = defaultTypeDefault(R),
2025-02-21 23:15:59 -07:00
/// 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,
2025-03-14 00:17:58 -06:00
const Self = @This();
pub const Result = ScryResultType(R);
pub const param_type: ParameterType = .option;
pub const multi_mode: MultiMode = scryMode(R);
// accessors to easily read decls from an instance
pub fn Type(comptime _: *const Self) type {
return Self.Result;
}
pub fn mode(comptime _: *const Self) MultiMode {
return Self.multi_mode;
}
pub fn shorts(comptime self: Self) []const OptScope {
comptime return if (self.short) |short|
&[_]OptScope{.{ .opt = mem.encodeShort(short), .scope = self.scope, .value = true }}
else
&.{};
}
pub fn longs(comptime self: Self) []const OptScope {
comptime return if (self.long) |long|
&[_]OptScope{.{ .opt = long, .scope = self.scope, .value = true }}
else
&.{};
}
2025-02-21 23:15:59 -07:00
};
}
pub fn Argument(comptime R: type) type {
return struct {
2025-03-14 00:17:58 -06:00
description: []const u8 = "",
2025-03-06 00:45:36 -07:00
const Self = @This();
pub const Result = ScryResultType(R);
pub const param_type: ParameterType = .argument;
pub const multi_mode: MultiMode = scryMode(R);
// accessors to easily read decls from an instance
pub fn Type(comptime _: *const Self) type {
return Self.Result;
}
pub fn mode(comptime _: *const Self) MultiMode {
return Self.multi_mode;
}
2025-02-21 23:15:59 -07:00
2025-03-14 00:17:58 -06:00
pub fn shorts(_: Self) []const OptScope {
return &.{};
}
pub fn longs(_: Self) []const OptScope {
return &.{};
}
2025-02-21 23:15:59 -07:00
};
}
2025-03-06 00:45:36 -07:00
pub fn Group(comptime R: type) type {
return struct {
2025-03-14 00:17:58 -06:00
description: []const u8 = "",
env: ?[]const u8 = null,
/// at least one of the parameters in the group must be provided
required: bool = false,
// if set, overrides the scope of all parameters
scope: ?Scope = null,
parameters: type,
2025-03-06 00:45:36 -07:00
const Self = @This();
pub const Result = ScryResultType(R);
pub const multi_mode: MultiMode = scryMode(R);
pub const param_type: ParameterType = .group;
// accessors to easily read decls from an instance
pub fn Type(comptime _: *const Self) type {
return Self.Result;
}
pub fn mode(comptime _: *const Self) MultiMode {
return Self.multi_mode;
}
2025-03-14 00:17:58 -06:00
pub fn shorts(comptime self: Self) []const OptScope {
comptime {
var list: []const OptScope = &.{};
for (@typeInfo(self.parameters).@"struct".decls) |decl| {
const param = @field(self.parameters, decl.name);
if (self.scope) |scope| {
for (param.shorts()) |short|
list = list ++ &[_]OptScope{.{ .opt = short.opt, .scope = scope, .value = short.value }};
} else {
list = list ++ param.shorts();
}
}
return list;
}
}
pub fn longs(comptime self: Self) []const OptScope {
comptime {
var list: []const OptScope = &.{};
for (@typeInfo(self.parameters).@"struct".decls) |decl| {
const param = @field(self.parameters, decl.name);
if (self.scope) |scope| {
for (param.longs()) |long|
list = list ++ &[_]OptScope{.{ .opt = long.opt, .scope = scope, .value = long.value }};
} else {
list = list ++ param.longs();
}
}
return list;
}
}
2025-03-06 00:45:36 -07:00
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);
}
}
}
};
}
fn hasCanary(comptime T: type) bool {
2025-03-14 00:17:58 -06:00
return @typeInfo(T) == .@"struct" and
@hasDecl(T, "__noclip_canary__") and
T.__noclip_canary__ == __Canary;
2025-03-06 00:45:36 -07:00
}
pub fn scryMode(comptime T: type) MultiMode {
return if (hasCanary(T))
T.multi_mode
else
.last;
}
pub fn ScryResultType(comptime T: type) type {
return if (hasCanary(T)) switch (T.multi_mode) {
.accumulate => []const T.Result,
.count, .first, .last => T.Result,
} else T;
}
pub fn DefaultType(comptime T: type) type {
return if (hasCanary(T)) switch (T.multi_mode) {
.accumulate => []const T.Result,
.count => T.Result,
.first, .last => ?T.Result,
} else ?T;
}
pub fn defaultTypeDefault(comptime T: type) DefaultType(T) {
return if (hasCanary(T)) switch (T.multi_mode) {
.accumulate => &.{},
.count => 0,
.first, .last => null,
} else null;
}
pub fn execute(comptime spec: type, args: ExecArgs(spec)) void {
var parser = Parser(spec, true).init(args.alloc, args.context);
2025-02-21 23:15:59 -07:00
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");
2025-03-14 00:17:58 -06:00
pub const mem = @import("./mem.zig");
2025-02-21 23:15:59 -07:00
const std = @import("std");