aboutsummaryrefslogtreecommitdiff
path: root/src/deps/zig-clap
diff options
context:
space:
mode:
Diffstat (limited to 'src/deps/zig-clap')
m---------src/deps/zig-clap0
-rw-r--r--src/deps/zig-clap/.gitignore1
-rw-r--r--src/deps/zig-clap/LICENSE24
-rw-r--r--src/deps/zig-clap/build.zig55
-rw-r--r--src/deps/zig-clap/clap.zig546
-rw-r--r--src/deps/zig-clap/clap/args.zig337
-rw-r--r--src/deps/zig-clap/clap/comptime.zig194
-rw-r--r--src/deps/zig-clap/clap/streaming.zig430
-rw-r--r--src/deps/zig-clap/gyro.zzz14
-rw-r--r--src/deps/zig-clap/zig.mod5
10 files changed, 1606 insertions, 0 deletions
diff --git a/src/deps/zig-clap b/src/deps/zig-clap
deleted file mode 160000
-Subproject 9c3ac846121a03934c7460cc54989059b1f66b2
diff --git a/src/deps/zig-clap/.gitignore b/src/deps/zig-clap/.gitignore
new file mode 100644
index 000000000..2040c29db
--- /dev/null
+++ b/src/deps/zig-clap/.gitignore
@@ -0,0 +1 @@
+zig-cache
diff --git a/src/deps/zig-clap/LICENSE b/src/deps/zig-clap/LICENSE
new file mode 100644
index 000000000..cf1ab25da
--- /dev/null
+++ b/src/deps/zig-clap/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/src/deps/zig-clap/build.zig b/src/deps/zig-clap/build.zig
new file mode 100644
index 000000000..5ab66da8a
--- /dev/null
+++ b/src/deps/zig-clap/build.zig
@@ -0,0 +1,55 @@
+const builtin = @import("builtin");
+const std = @import("std");
+
+const Builder = std.build.Builder;
+const Mode = std.builtin.Mode;
+
+pub fn build(b: *Builder) void {
+ const mode = b.standardReleaseOptions();
+ const target = b.standardTargetOptions(.{});
+
+ const test_all_step = b.step("test", "Run all tests in all modes.");
+ inline for ([_]Mode{ Mode.Debug, Mode.ReleaseFast, Mode.ReleaseSafe, Mode.ReleaseSmall }) |test_mode| {
+ const mode_str = comptime modeToString(test_mode);
+
+ const tests = b.addTest("clap.zig");
+ tests.setBuildMode(test_mode);
+ tests.setTarget(target);
+ tests.setNamePrefix(mode_str ++ " ");
+
+ const test_step = b.step("test-" ++ mode_str, "Run all tests in " ++ mode_str ++ ".");
+ test_step.dependOn(&tests.step);
+ test_all_step.dependOn(test_step);
+ }
+
+ const example_step = b.step("examples", "Build examples");
+ inline for ([_][]const u8{
+ "simple",
+ "simple-ex",
+ //"simple-error",
+ "streaming-clap",
+ "help",
+ "usage",
+ }) |example_name| {
+ const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig");
+ example.addPackagePath("clap", "clap.zig");
+ example.setBuildMode(mode);
+ example.setTarget(target);
+ example.install();
+ example_step.dependOn(&example.step);
+ }
+
+ const all_step = b.step("all", "Build everything and runs all tests");
+ all_step.dependOn(test_all_step);
+
+ b.default_step.dependOn(all_step);
+}
+
+fn modeToString(mode: Mode) []const u8 {
+ return switch (mode) {
+ Mode.Debug => "debug",
+ Mode.ReleaseFast => "release-fast",
+ Mode.ReleaseSafe => "release-safe",
+ Mode.ReleaseSmall => "release-small",
+ };
+}
diff --git a/src/deps/zig-clap/clap.zig b/src/deps/zig-clap/clap.zig
new file mode 100644
index 000000000..a21a1cb1a
--- /dev/null
+++ b/src/deps/zig-clap/clap.zig
@@ -0,0 +1,546 @@
+const std = @import("std");
+
+const debug = std.debug;
+const heap = std.heap;
+const io = std.io;
+const mem = std.mem;
+const testing = std.testing;
+
+pub const args = @import("clap/args.zig");
+
+test "clap" {
+ testing.refAllDecls(@This());
+}
+
+pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap;
+pub const StreamingClap = @import("clap/streaming.zig").StreamingClap;
+
+/// The names a ::Param can have.
+pub const Names = struct {
+ /// '-' prefix
+ short: ?u8 = null,
+
+ /// '--' prefix
+ long: ?[]const u8 = null,
+};
+
+/// Whether a param takes no value (a flag), one value, or can be specified multiple times.
+pub const Values = enum {
+ none,
+ one,
+ many,
+ one_optional,
+};
+
+/// Represents a parameter for the command line.
+/// Parameters come in three kinds:
+/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
+/// * They can take a value three different ways.
+/// * "-a value"
+/// * "-a=value"
+/// * "-avalue"
+/// * They chain if they don't take values: "-abc".
+/// * The last given parameter can take a value in the same way that a single parameter can:
+/// * "-abc value"
+/// * "-abc=value"
+/// * "-abcvalue"
+/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
+/// can describe the paramter.
+/// * They can take a value two different ways.
+/// * "--long-param value"
+/// * "--long-param=value"
+/// * Positional: Should be used as the primary parameter of the program, like a filename or
+/// an expression to parse.
+/// * Positional parameters have both names.long and names.short == null.
+/// * Positional parameters must take a value.
+pub fn Param(comptime Id: type) type {
+ return struct {
+ id: Id = std.mem.zeroes(Id),
+ names: Names = std.mem.zeroes(Names),
+ takes_value: Values = .none,
+ };
+}
+
+/// Takes a string and parses it to a Param(Help).
+/// This is the reverse of 'help' but for at single parameter only.
+pub fn parseParam(line: []const u8) !Param(Help) {
+ @setEvalBranchQuota(999999);
+
+ var found_comma = false;
+ var it = mem.tokenize(u8, line, " \t");
+ var param_str = it.next() orelse return error.NoParamFound;
+
+ const short_name = if (!mem.startsWith(u8, param_str, "--") and
+ mem.startsWith(u8, param_str, "-"))
+ blk: {
+ found_comma = param_str[param_str.len - 1] == ',';
+ if (found_comma)
+ param_str = param_str[0 .. param_str.len - 1];
+
+ if (param_str.len != 2)
+ return error.InvalidShortParam;
+
+ const short_name = param_str[1];
+ if (!found_comma) {
+ var res = parseParamRest(it.rest());
+ res.names.short = short_name;
+ return res;
+ }
+
+ param_str = it.next() orelse return error.NoParamFound;
+ break :blk short_name;
+ } else null;
+
+ _ = if (mem.startsWith(u8, param_str, "--")) {
+ if (param_str[param_str.len - 1] == ',')
+ return error.TrailingComma;
+ } else if (found_comma) {
+ return error.TrailingComma;
+ } else if (short_name == null) {
+ return parseParamRest(mem.trimLeft(u8, line, " \t"));
+ } else null;
+
+ var res = parseParamRest(it.rest());
+ res.names.long = param_str[2..];
+ res.names.short = short_name;
+ return res;
+}
+
+fn parseParamRest(line: []const u8) Param(Help) {
+ if (mem.startsWith(u8, line, "<")) blk: {
+ const len = mem.indexOfScalar(u8, line, '>') orelse break :blk;
+ const takes_many = mem.startsWith(u8, line[len + 1 ..], "...");
+ const takes_one_optional = mem.startsWith(u8, line[len + 1 ..], "?");
+ const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many) + (@as(usize, 1) * @boolToInt(takes_one_optional));
+ return .{
+ .takes_value = if (takes_many) Values.many else if (takes_one_optional) Values.one_optional else Values.one,
+ .id = .{
+ .msg = mem.trim(u8, line[help_start..], " \t"),
+ .value = line[1..len],
+ },
+ };
+ }
+
+ return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } };
+}
+
+fn expectParam(expect: Param(Help), actual: Param(Help)) void {
+ testing.expectEqualStrings(expect.id.msg, actual.id.msg);
+ testing.expectEqualStrings(expect.id.value, actual.id.value);
+ testing.expectEqual(expect.names.short, actual.names.short);
+ testing.expectEqual(expect.takes_value, actual.takes_value);
+ if (expect.names.long) |long| {
+ testing.expectEqualStrings(long, actual.names.long.?);
+ } else {
+ testing.expectEqual(@as(?[]const u8, null), actual.names.long);
+ }
+}
+
+test "parseParam" {
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "value" },
+ .names = .{ .short = 's', .long = "long" },
+ .takes_value = .one,
+ }, try parseParam("-s, --long <value> Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "value" },
+ .names = .{ .short = 's', .long = "long" },
+ .takes_value = .many,
+ }, try parseParam("-s, --long <value>... Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "value" },
+ .names = .{ .long = "long" },
+ .takes_value = .one,
+ }, try parseParam("--long <value> Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "value" },
+ .names = .{ .short = 's' },
+ .takes_value = .one,
+ }, try parseParam("-s <value> Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text" },
+ .names = .{ .short = 's', .long = "long" },
+ }, try parseParam("-s, --long Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text" },
+ .names = .{ .short = 's' },
+ }, try parseParam("-s Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text" },
+ .names = .{ .long = "long" },
+ }, try parseParam("--long Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "A | B" },
+ .names = .{ .long = "long" },
+ .takes_value = .one,
+ }, try parseParam("--long <A | B> Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "A" },
+ .names = .{},
+ .takes_value = .one,
+ }, try parseParam("<A> Help text"));
+
+ expectParam(Param(Help){
+ .id = .{ .msg = "Help text", .value = "A" },
+ .names = .{},
+ .takes_value = .many,
+ }, try parseParam("<A>... Help text"));
+
+ testing.expectError(error.TrailingComma, parseParam("--long, Help"));
+ testing.expectError(error.TrailingComma, parseParam("-s, Help"));
+ testing.expectError(error.InvalidShortParam, parseParam("-ss Help"));
+ testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help"));
+ testing.expectError(error.InvalidShortParam, parseParam("- Help"));
+}
+
+/// Optional diagnostics used for reporting useful errors
+pub const Diagnostic = struct {
+ arg: []const u8 = "",
+ name: Names = Names{},
+
+ /// Default diagnostics reporter when all you want is English with no colors.
+ /// Use this as a reference for implementing your own if needed.
+ pub fn report(diag: Diagnostic, stream: anytype, err: anyerror) !void {
+ const Arg = struct {
+ prefix: []const u8,
+ name: []const u8,
+ };
+ const a = if (diag.name.short) |*c|
+ Arg{ .prefix = "-", .name = @as(*const [1]u8, c)[0..] }
+ else if (diag.name.long) |l|
+ Arg{ .prefix = "--", .name = l }
+ else
+ Arg{ .prefix = "", .name = diag.arg };
+
+ switch (err) {
+ error.DoesntTakeValue => try stream.print("The argument '{s}{s}' does not take a value\n", .{ a.prefix, a.name }),
+ error.MissingValue => try stream.print("The argument '{s}{s}' requires a value but none was supplied\n", .{ a.prefix, a.name }),
+ error.InvalidArgument => if (a.prefix.len > 0 and a.name.len > 0)
+ try stream.print("Invalid argument '{s}{s}'\n", .{ a.prefix, a.name })
+ else
+ try stream.print("Failed to parse argument due to unexpected single dash\n", .{}),
+ else => try stream.print("Error while parsing arguments: {s}\n", .{@errorName(err)}),
+ }
+ }
+};
+
+fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) void {
+ var buf: [1024]u8 = undefined;
+ var slice_stream = io.fixedBufferStream(&buf);
+ diag.report(slice_stream.writer(), err) catch unreachable;
+ testing.expectEqualStrings(expected, slice_stream.getWritten());
+}
+
+pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type {
+ return struct {
+ arena: std.heap.ArenaAllocator,
+ clap: ComptimeClap(Id, params),
+ exe_arg: ?[]const u8,
+
+ pub fn deinit(a: *@This()) void {
+ a.arena.deinit();
+ }
+
+ pub fn flag(a: @This(), comptime name: []const u8) bool {
+ return a.clap.flag(name);
+ }
+
+ pub fn option(a: @This(), comptime name: []const u8) ?[]const u8 {
+ return a.clap.option(name);
+ }
+
+ pub fn options(a: @This(), comptime name: []const u8) []const []const u8 {
+ return a.clap.options(name);
+ }
+
+ pub fn positionals(a: @This()) []const []const u8 {
+ return a.clap.positionals();
+ }
+
+ pub fn remaining(a: @This()) []const []const u8 {
+ return a.clap.remaining();
+ }
+
+ pub fn hasFlag(comptime name: []const u8) bool {
+ return ComptimeClap(Id, params).hasFlag(name);
+ }
+ };
+}
+
+/// Options that can be set to customize the behavior of parsing.
+pub const ParseOptions = struct {
+ /// The allocator used for all memory allocations. Defaults to the `heap.page_allocator`.
+ /// Note: You should probably override this allocator if you are calling `parseEx`. Unlike
+ /// `parse`, `parseEx` does not wrap the allocator so the heap allocator can be
+ /// quite expensive. (TODO: Can we pick a better default? For `parse`, this allocator
+ /// is fine, as it wraps it in an arena)
+ allocator: mem.Allocator = heap.page_allocator,
+ diagnostic: ?*Diagnostic = null,
+ stop_after_positional_at: usize = 0,
+};
+
+/// Same as `parseEx` but uses the `args.OsIterator` by default.
+pub fn parse(
+ comptime Id: type,
+ comptime params: []const Param(Id),
+ opt: ParseOptions,
+) !Args(Id, params) {
+ var iter = args.OsIterator.init(opt.allocator);
+ var res = Args(Id, params){
+ .arena = iter.arena,
+ .exe_arg = iter.exe_arg,
+ .clap = undefined,
+ };
+
+ // Let's reuse the arena from the `OSIterator` since we already have
+ // it.
+ res.clap = try parseEx(Id, params, &iter, .{
+ .allocator = res.arena.allocator(),
+ .diagnostic = opt.diagnostic,
+ .stop_after_positional_at = opt.stop_after_positional_at,
+ });
+ return res;
+}
+
+/// Parses the command line arguments passed into the program based on an
+/// array of `Param`s.
+pub fn parseEx(
+ comptime Id: type,
+ comptime params: []const Param(Id),
+ iter: anytype,
+ opt: ParseOptions,
+) !ComptimeClap(Id, params) {
+ const Clap = ComptimeClap(Id, params);
+ return try Clap.parse(iter, opt);
+}
+
+/// Will print a help message in the following format:
+/// -s, --long <valueText> helpText
+/// -s, helpText
+/// -s <valueText> helpText
+/// --long helpText
+/// --long <valueText> helpText
+pub fn helpFull(
+ stream: anytype,
+ comptime Id: type,
+ params: []const Param(Id),
+ comptime Error: type,
+ context: anytype,
+ helpText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
+ valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
+) !void {
+ const max_spacing = blk: {
+ var res: usize = 0;
+ for (params) |param| {
+ var cs = io.countingWriter(io.null_writer);
+ try printParam(cs.writer(), Id, param, Error, context, valueText);
+ if (res < cs.bytes_written)
+ res = @intCast(usize, cs.bytes_written);
+ }
+
+ break :blk res;
+ };
+
+ for (params) |param| {
+ if (param.names.short == null and param.names.long == null)
+ continue;
+
+ var cs = io.countingWriter(stream);
+ try stream.print("\t", .{});
+ try printParam(cs.writer(), Id, param, Error, context, valueText);
+ try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written));
+ try stream.print("\t{s}\n", .{try helpText(context, param)});
+ }
+}
+
+fn printParam(
+ stream: anytype,
+ comptime Id: type,
+ param: Param(Id),
+ comptime Error: type,
+ context: anytype,
+ valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
+) !void {
+ if (param.names.short) |s| {
+ try stream.print("-{c}", .{s});
+ } else {
+ try stream.print(" ", .{});
+ }
+ if (param.names.long) |l| {
+ if (param.names.short) |_| {
+ try stream.print(", ", .{});
+ } else {
+ try stream.print(" ", .{});
+ }
+
+ try stream.print("--{s}", .{l});
+ }
+
+ switch (param.takes_value) {
+ .none => {},
+ .one => try stream.print(" <{s}>", .{try valueText(context, param)}),
+ .one_optional => try stream.print(" <{s}>?", .{try valueText(context, param)}),
+ .many => try stream.print(" <{s}>...", .{try valueText(context, param)}),
+ }
+}
+
+/// A wrapper around helpFull for simple helpText and valueText functions that
+/// cant return an error or take a context.
+pub fn helpEx(
+ stream: anytype,
+ comptime Id: type,
+ params: []const Param(Id),
+ helpText: *const fn (Param(Id)) []const u8,
+ valueText: *const fn (Param(Id)) []const u8,
+) !void {
+ const Context = struct {
+ helpText: *const fn (Param(Id)) []const u8,
+ valueText: *const fn (Param(Id)) []const u8,
+
+ pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 {
+ return c.helpText(p);
+ }
+
+ pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
+ return c.valueText(p);
+ }
+ };
+
+ return helpFull(
+ stream,
+ Id,
+ params,
+ error{},
+ Context{
+ .helpText = helpText,
+ .valueText = valueText,
+ },
+ Context.help,
+ Context.value,
+ );
+}
+
+pub const Help = struct {
+ msg: []const u8 = "",
+ value: []const u8 = "",
+};
+
+/// A wrapper around helpEx that takes a Param(Help).
+pub fn help(stream: anytype, params: []const Param(Help)) !void {
+ try helpEx(stream, Help, params, getHelpSimple, getValueSimple);
+}
+
+fn getHelpSimple(param: Param(Help)) []const u8 {
+ return param.id.msg;
+}
+
+fn getValueSimple(param: Param(Help)) []const u8 {
+ return param.id.value;
+}
+
+/// Will print a usage message in the following format:
+/// [-abc] [--longa] [-d <valueText>] [--longb <valueText>] <valueText>
+///
+/// First all none value taking parameters, which have a short name are
+/// printed, then non positional parameters and finally the positinal.
+pub fn usageFull(
+ stream: anytype,
+ comptime Id: type,
+ params: []const Param(Id),
+ comptime Error: type,
+ context: anytype,
+ valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
+) !void {
+ var cos = io.countingWriter(stream);
+ const cs = cos.writer();
+ for (params) |param| {
+ const name = param.names.short orelse continue;
+ if (param.takes_value != .none)
+ continue;
+
+ if (cos.bytes_written == 0)
+ try stream.writeAll("[-");
+ try cs.writeByte(name);
+ }
+ if (cos.bytes_written != 0)
+ try cs.writeByte(']');
+
+ var positional: ?Param(Id) = null;
+ for (params) |param| {
+ if (param.takes_value == .none and param.names.short != null)
+ continue;
+
+ const prefix = if (param.names.short) |_| "-" else "--";
+
+ // Seems the zig compiler is being a little wierd. I doesn't allow me to write
+ // @as(*const [1]u8, s) VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
+ const name = if (param.names.short) |*s| @ptrCast([*]const u8, s)[0..1] else param.names.long orelse {
+ positional = param;
+ continue;
+ };
+ if (cos.bytes_written != 0)
+ try cs.writeByte(' ');
+
+ try cs.print("[{s}{s}", .{ prefix, name });
+ switch (param.takes_value) {
+ .none => {},
+ .one => try cs.print(" <{s}>", .{try valueText(context, param)}),
+ .one_optional => try cs.print(" <{s}>?", .{try valueText(context, param)}),
+ .many => try cs.print(" <{s}>...", .{try valueText(context, param)}),
+ }
+
+ try cs.writeByte(']');
+ }
+
+ if (positional) |p| {
+ if (cos.bytes_written != 0)
+ try cs.writeByte(' ');
+ try cs.print("<{s}>", .{try valueText(context, p)});
+ }
+}
+
+/// A wrapper around usageFull for a simple valueText functions that
+/// cant return an error or take a context.
+pub fn usageEx(
+ stream: anytype,
+ comptime Id: type,
+ params: []const Param(Id),
+ valueText: fn (Param(Id)) []const u8,
+) !void {
+ const Context = struct {
+ valueText: fn (Param(Id)) []const u8,
+
+ pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
+ return c.valueText(p);
+ }
+ };
+
+ return usageFull(
+ stream,
+ Id,
+ params,
+ error{},
+ Context{ .valueText = valueText },
+ Context.value,
+ );
+}
+
+/// A wrapper around usageEx that takes a Param(Help).
+pub fn usage(stream: anytype, params: []const Param(Help)) !void {
+ try usageEx(stream, Help, params, getValueSimple);
+}
+
+fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
+ var buf: [1024]u8 = undefined;
+ var fbs = io.fixedBufferStream(&buf);
+ try usage(fbs.writer(), params);
+ testing.expectEqualStrings(expected, fbs.getWritten());
+}
diff --git a/src/deps/zig-clap/clap/args.zig b/src/deps/zig-clap/clap/args.zig
new file mode 100644
index 000000000..a1fa3773a
--- /dev/null
+++ b/src/deps/zig-clap/clap/args.zig
@@ -0,0 +1,337 @@
+const std = @import("std");
+
+const builtin = @import("builtin");
+const debug = std.debug;
+const heap = std.heap;
+const mem = std.mem;
+const process = std.process;
+const testing = std.testing;
+
+/// An example of what methods should be implemented on an arg iterator.
+pub const ExampleArgIterator = struct {
+ const Error = error{};
+
+ pub fn next(_: *ExampleArgIterator) Error!?[]const u8 {
+ return "2";
+ }
+};
+
+/// An argument iterator which iterates over a slice of arguments.
+/// This implementation does not allocate.
+pub const SliceIterator = struct {
+ const Error = error{};
+
+ args: []const []const u8,
+ index: usize = 0,
+
+ pub fn next(iter: *SliceIterator) Error!?[]const u8 {
+ if (iter.args.len <= iter.index)
+ return null;
+
+ defer iter.index += 1;
+ return iter.args[iter.index];
+ }
+};
+
+test "SliceIterator" {
+ const args = &[_][]const u8{ "A", "BB", "CCC" };
+ var iter = SliceIterator{ .args = args };
+
+ for (args) |a| {
+ const b = try iter.next();
+ debug.assert(mem.eql(u8, a, b.?));
+ }
+}
+
+/// An argument iterator which wraps the ArgIterator in ::std.
+/// On windows, this iterator allocates.
+pub const OsIterator = struct {
+ const Error = process.ArgIterator.InitError;
+
+ arena: heap.ArenaAllocator,
+ args: process.ArgIterator,
+
+ /// The executable path (this is the first argument passed to the program)
+ /// TODO: Is it the right choice for this to be null? Maybe `init` should
+ /// return an error when we have no exe.
+ exe_arg: ?[:0]const u8,
+
+ pub fn init(allocator: mem.Allocator) OsIterator {
+ var res = OsIterator{
+ .arena = heap.ArenaAllocator.init(allocator),
+ .args = process.args(),
+ .exe_arg = undefined,
+ };
+ res.exe_arg = res.next();
+ return res;
+ }
+
+ pub fn deinit(iter: *OsIterator) void {
+ iter.arena.deinit();
+ }
+
+ pub fn next(iter: *OsIterator) ?[:0]const u8 {
+ return iter.args.next();
+ }
+};
+
+/// An argument iterator that takes a string and parses it into arguments, simulating
+/// how shells split arguments.
+pub const ShellIterator = struct {
+ const Error = error{
+ DanglingEscape,
+ QuoteNotClosed,
+ } || mem.Allocator.Error;
+
+ arena: heap.ArenaAllocator,
+ str: []const u8,
+
+ pub fn init(allocator: mem.Allocator, str: []const u8) ShellIterator {
+ return .{
+ .arena = heap.ArenaAllocator.init(allocator),
+ .str = str,
+ };
+ }
+
+ pub fn deinit(iter: *ShellIterator) void {
+ iter.arena.deinit();
+ }
+
+ pub fn next(iter: *ShellIterator) Error!?[]const u8 {
+ // Whenever possible, this iterator will return slices into `str` instead of
+ // allocating. Sometimes this is not possible, for example, escaped characters
+ // have be be unescape, so we need to allocate in this case.
+ var list = std.ArrayList(u8).init(&iter.arena.allocator);
+ var start: usize = 0;
+ var state: enum {
+ skip_whitespace,
+ no_quote,
+ no_quote_escape,
+ single_quote,
+ double_quote,
+ double_quote_escape,
+ after_quote,
+ } = .skip_whitespace;
+
+ for (iter.str, 0..) |c, i| {
+ switch (state) {
+ // The state that skips the initial whitespace.
+ .skip_whitespace => switch (c) {
+ ' ', '\t', '\n' => {},
+ '\'' => {
+ start = i + 1;
+ state = .single_quote;
+ },
+ '"' => {
+ start = i + 1;
+ state = .double_quote;
+ },
+ '\\' => {
+ start = i + 1;
+ state = .no_quote_escape;
+ },
+ else => {
+ start = i;
+ state = .no_quote;
+ },
+ },
+
+ // The state that parses the none quoted part of a argument.
+ .no_quote => switch (c) {
+ // We're done parsing a none quoted argument when we hit a
+ // whitespace.
+ ' ', '\t', '\n' => {
+ defer iter.str = iter.str[i..];
+ return iter.result(start, i, &list);
+ },
+
+ // Slicing is not possible if a quote starts while parsing none
+ // quoted args.
+ // Example:
+ // ab'cd' -> abcd
+ '\'' => {
+ try list.appendSlice(iter.str[start..i]);
+ start = i + 1;
+ state = .single_quote;
+ },
+ '"' => {
+ try list.appendSlice(iter.str[start..i]);
+ start = i + 1;
+ state = .double_quote;
+ },
+
+ // Slicing is not possible if we need to escape a character.
+ // Example:
+ // ab\"d -> ab"d
+ '\\' => {
+ try list.appendSlice(iter.str[start..i]);
+ start = i + 1;
+ state = .no_quote_escape;
+ },
+ else => {},
+ },
+
+ // We're in this state after having parsed the quoted part of an
+ // argument. This state works mostly the same as .no_quote, but
+ // is aware, that the last character seen was a quote, which should
+ // not be part of the argument. This is why you will see `i - 1` here
+ // instead of just `i` when `iter.str` is sliced.
+ .after_quote => switch (c) {
+ ' ', '\t', '\n' => {
+ defer iter.str = iter.str[i..];
+ return iter.result(start, i - 1, &list);
+ },
+ '\'' => {
+ try list.appendSlice(iter.str[start .. i - 1]);
+ start = i + 1;
+ state = .single_quote;
+ },
+ '"' => {
+ try list.appendSlice(iter.str[start .. i - 1]);
+ start = i + 1;
+ state = .double_quote;
+ },
+ '\\' => {
+ try list.appendSlice(iter.str[start .. i - 1]);
+ start = i + 1;
+ state = .no_quote_escape;
+ },
+ else => {
+ try list.appendSlice(iter.str[start .. i - 1]);
+ start = i;
+ state = .no_quote;
+ },
+ },
+
+ // The states that parse the quoted part of arguments. The only differnece
+ // between single and double quoted arguments is that single quoted
+ // arguments ignore escape sequences, while double quoted arguments
+ // does escaping.
+ .single_quote => switch (c) {
+ '\'' => state = .after_quote,
+ else => {},
+ },
+ .double_quote => switch (c) {
+ '"' => state = .after_quote,
+ '\\' => {
+ try list.appendSlice(iter.str[start..i]);
+ start = i + 1;
+ state = .double_quote_escape;
+ },
+ else => {},
+ },
+
+ // The state we end up when after the escape character (`\`). All these
+ // states do is transition back into the previous state.
+ // TODO: Are there any escape sequences that does transform the second
+ // character into something else? For example, in Zig, `\n` is
+ // transformed into the line feed ascii character.
+ .no_quote_escape => switch (c) {
+ else => state = .no_quote,
+ },
+ .double_quote_escape => switch (c) {
+ else => state = .double_quote,
+ },
+ }
+ }
+
+ defer iter.str = iter.str[iter.str.len..];
+ switch (state) {
+ .skip_whitespace => return null,
+ .no_quote => return iter.result(start, iter.str.len, &list),
+ .after_quote => return iter.result(start, iter.str.len - 1, &list),
+ .no_quote_escape => return Error.DanglingEscape,
+ .single_quote,
+ .double_quote,
+ .double_quote_escape,
+ => return Error.QuoteNotClosed,
+ }
+ }
+
+ fn result(iter: *ShellIterator, start: usize, end: usize, list: *std.ArrayList(u8)) Error!?[]const u8 {
+ const res = iter.str[start..end];
+
+ // If we already have something in `list` that means that we could not
+ // parse the argument without allocation. We therefor need to just append
+ // the rest we have to the list and return that.
+ if (list.items.len != 0) {
+ try list.appendSlice(res);
+ return try list.toOwnedSlice();
+ }
+ return res;
+ }
+};
+
+fn testShellIteratorOk(str: []const u8, allocations: usize, expect: []const []const u8) void {
+ var allocator = testing.FailingAllocator.init(testing.allocator, allocations);
+ var it = ShellIterator.init(&allocator.allocator, str);
+ defer it.deinit();
+
+ for (expect) |e| {
+ if (it.next()) |actual| {
+ testing.expect(actual != null);
+ testing.expectEqualStrings(e, actual.?);
+ } else |err| testing.expectEqual(@as(anyerror![]const u8, e), err);
+ }
+
+ if (it.next()) |actual| {
+ testing.expectEqual(@as(?[]const u8, null), actual);
+ testing.expectEqual(allocations, allocator.allocations);
+ } else |err| testing.expectEqual(@as(anyerror!void, {}), err);
+}
+
+fn testShellIteratorErr(str: []const u8, expect: anyerror) void {
+ var it = ShellIterator.init(testing.allocator, str);
+ defer it.deinit();
+
+ while (it.next() catch |err| {
+ testing.expectError(expect, @as(anyerror!void, err));
+ return;
+ }) |_| {}
+
+ testing.expectError(expect, @as(anyerror!void, {}));
+}
+
+test "ShellIterator" {
+ testShellIteratorOk("a", 0, &[_][]const u8{"a"});
+ testShellIteratorOk("'a'", 0, &[_][]const u8{"a"});
+ testShellIteratorOk("\"a\"", 0, &[_][]const u8{"a"});
+ testShellIteratorOk("a b", 0, &[_][]const u8{ "a", "b" });
+ testShellIteratorOk("'a' b", 0, &[_][]const u8{ "a", "b" });
+ testShellIteratorOk("\"a\" b", 0, &[_][]const u8{ "a", "b" });
+ testShellIteratorOk("a 'b'", 0, &[_][]const u8{ "a", "b" });
+ testShellIteratorOk("a \"b\"", 0, &[_][]const u8{ "a", "b" });
+ testShellIteratorOk("'a b'", 0, &[_][]const u8{"a b"});
+ testShellIteratorOk("\"a b\"", 0, &[_][]const u8{"a b"});
+ testShellIteratorOk("\"a\"\"b\"", 1, &[_][]const u8{"ab"});
+ testShellIteratorOk("'a''b'", 1, &[_][]const u8{"ab"});
+ testShellIteratorOk("'a'b", 1, &[_][]const u8{"ab"});
+ testShellIteratorOk("a'b'", 1, &[_][]const u8{"ab"});
+ testShellIteratorOk("a\\ b", 1, &[_][]const u8{"a b"});
+ testShellIteratorOk("\"a\\ b\"", 1, &[_][]const u8{"a b"});
+ testShellIteratorOk("'a\\ b'", 0, &[_][]const u8{"a\\ b"});
+ testShellIteratorOk(" a b ", 0, &[_][]const u8{ "a", "b" });
+ testShellIteratorOk("\\ \\ ", 0, &[_][]const u8{ " ", " " });
+
+ testShellIteratorOk(
+ \\printf 'run\nuninstall\n'
+ , 0, &[_][]const u8{ "printf", "run\\nuninstall\\n" });
+ testShellIteratorOk(
+ \\setsid -f steam "steam://$action/$id"
+ , 0, &[_][]const u8{ "setsid", "-f", "steam", "steam://$action/$id" });
+ testShellIteratorOk(
+ \\xargs -I% rg --no-heading --no-line-number --only-matching
+ \\ --case-sensitive --multiline --text --byte-offset '(?-u)%' $@
+ \\
+ , 0, &[_][]const u8{
+ "xargs", "-I%", "rg", "--no-heading",
+ "--no-line-number", "--only-matching", "--case-sensitive", "--multiline",
+ "--text", "--byte-offset", "(?-u)%", "$@",
+ });
+
+ testShellIteratorErr("'a", error.QuoteNotClosed);
+ testShellIteratorErr("'a\\", error.QuoteNotClosed);
+ testShellIteratorErr("\"a", error.QuoteNotClosed);
+ testShellIteratorErr("\"a\\", error.QuoteNotClosed);
+ testShellIteratorErr("a\\", error.DanglingEscape);
+}
diff --git a/src/deps/zig-clap/clap/comptime.zig b/src/deps/zig-clap/clap/comptime.zig
new file mode 100644
index 000000000..3dcd4f7d7
--- /dev/null
+++ b/src/deps/zig-clap/clap/comptime.zig
@@ -0,0 +1,194 @@
+const clap = @import("../clap.zig");
+const std = @import("std");
+
+const debug = std.debug;
+const heap = std.heap;
+const mem = std.mem;
+const testing = std.testing;
+
+/// Deprecated: Use `parseEx` instead
+pub fn ComptimeClap(
+ comptime Id: type,
+ comptime params: []const clap.Param(Id),
+) type {
+ var _flags: usize = 0;
+ var _single_options: usize = 0;
+ var _multi_options: usize = 0;
+ var _converted_params: []const clap.Param(usize) = &[_]clap.Param(usize){};
+ for (params) |param| {
+ var index: usize = 0;
+ if (param.names.long != null or param.names.short != null) {
+ const ptr = switch (param.takes_value) {
+ .none => &_flags,
+ .one_optional, .one => &_single_options,
+ .many => &_multi_options,
+ };
+ index = ptr.*;
+ ptr.* += 1;
+ }
+
+ const converted = clap.Param(usize){
+ .id = index,
+ .names = param.names,
+ .takes_value = param.takes_value,
+ };
+ _converted_params = _converted_params ++ [_]clap.Param(usize){converted};
+ }
+ const flags = _flags;
+ const single_options = _single_options;
+ const multi_options = _multi_options;
+ const converted_params = _converted_params;
+
+ return struct {
+ single_options: [single_options]?[]const u8,
+ multi_options: [multi_options][]const []const u8,
+ flags: [flags]bool,
+ pos: []const []const u8,
+ passthrough_positionals: []const []const u8,
+ allocator: mem.Allocator,
+
+ pub fn parse(iter: anytype, opt: clap.ParseOptions) !@This() {
+ const allocator = opt.allocator;
+ var multis = [_]std.ArrayList([]const u8){undefined} ** multi_options;
+ for (&multis) |*multi| {
+ multi.* = std.ArrayList([]const u8).init(allocator);
+ }
+
+ var pos = std.ArrayList([]const u8).init(allocator);
+ var passthrough_positionals = std.ArrayList([]const u8).init(allocator);
+
+ var res = @This(){
+ .single_options = [_]?[]const u8{null} ** single_options,
+ .multi_options = [_][]const []const u8{undefined} ** multi_options,
+ .flags = [_]bool{false} ** flags,
+ .pos = undefined,
+ .allocator = allocator,
+ .passthrough_positionals = undefined,
+ };
+
+ var stream = clap.StreamingClap(usize, @typeInfo(@TypeOf(iter)).Pointer.child){
+ .params = converted_params,
+ .iter = iter,
+ };
+
+ while (try stream.next()) |arg| {
+ const param = arg.param;
+ if (param.names.long == null and param.names.short == null) {
+ try pos.append(arg.value.?);
+ if (opt.stop_after_positional_at > 0 and pos.items.len >= opt.stop_after_positional_at) {
+ const bun = @import("bun");
+ if (comptime bun.Environment.isWindows) @compileError(
+ "TODO: implement stop_after_positional_at on windows",
+ );
+
+ var remaining_ = std.os.argv[@min(std.os.argv.len, stream.iter.args.inner.index)..];
+ const first: []const u8 = if (remaining_.len > 0) bun.span(remaining_[0]) else "";
+ if (first.len > 0 and std.mem.eql(u8, first, "--")) {
+ remaining_ = remaining_[1..];
+ }
+
+ try passthrough_positionals.ensureTotalCapacityPrecise(remaining_.len);
+ for (remaining_) |arg_| {
+ // use bun.span due to the optimization for long strings
+ passthrough_positionals.appendAssumeCapacity(bun.span(arg_));
+ }
+ break;
+ }
+ } else if (param.takes_value == .one or param.takes_value == .one_optional) {
+ debug.assert(res.single_options.len != 0);
+ if (res.single_options.len != 0)
+ res.single_options[param.id] = arg.value orelse "";
+ } else if (param.takes_value == .many) {
+ debug.assert(multis.len != 0);
+ if (multis.len != 0)
+ try multis[param.id].append(arg.value.?);
+ } else {
+ debug.assert(res.flags.len != 0);
+ if (res.flags.len != 0)
+ res.flags[param.id] = true;
+ }
+ }
+
+ for (&multis, 0..) |*multi, i|
+ res.multi_options[i] = try multi.toOwnedSlice();
+ res.pos = try pos.toOwnedSlice();
+ res.passthrough_positionals = try passthrough_positionals.toOwnedSlice();
+ return res;
+ }
+
+ pub fn deinit(parser: @This()) void {
+ for (parser.multi_options) |o|
+ parser.allocator.free(o);
+ parser.allocator.free(parser.pos);
+ }
+
+ pub fn flag(parser: @This(), comptime name: []const u8) bool {
+ const param = comptime findParam(name);
+ if (param.takes_value != .none and param.takes_value != .one_optional)
+ @compileError(name ++ " is an option and not a flag.");
+
+ return parser.flags[param.id];
+ }
+
+ pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
+ const param = comptime findParam(name);
+ if (param.takes_value == .none)
+ @compileError(name ++ " is a flag and not an option.");
+ if (param.takes_value == .many)
+ @compileError(name ++ " takes many options, not one.");
+ return parser.single_options[param.id];
+ }
+
+ pub fn options(parser: @This(), comptime name: []const u8) []const []const u8 {
+ const param = comptime findParam(name);
+ if (param.takes_value == .none)
+ @compileError(name ++ " is a flag and not an option.");
+ if (param.takes_value == .one or param.takes_value == .one_optional)
+ @compileError(name ++ " takes one option, not multiple.");
+
+ return parser.multi_options[param.id];
+ }
+
+ pub fn positionals(parser: @This()) []const []const u8 {
+ return parser.pos;
+ }
+
+ pub fn remaining(parser: @This()) []const []const u8 {
+ return parser.passthrough_positionals;
+ }
+
+ pub fn hasFlag(comptime name: []const u8) bool {
+ comptime {
+ for (converted_params) |param| {
+ if (param.names.short) |s| {
+ if (mem.eql(u8, name, "-" ++ [_]u8{s}))
+ return true;
+ }
+ if (param.names.long) |l| {
+ if (mem.eql(u8, name, "--" ++ l))
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ fn findParam(comptime name: []const u8) clap.Param(usize) {
+ comptime {
+ for (converted_params) |param| {
+ if (param.names.short) |s| {
+ if (mem.eql(u8, name, "-" ++ [_]u8{s}))
+ return param;
+ }
+ if (param.names.long) |l| {
+ if (mem.eql(u8, name, "--" ++ l))
+ return param;
+ }
+ }
+
+ @compileError(name ++ " is not a parameter.");
+ }
+ }
+ };
+}
diff --git a/src/deps/zig-clap/clap/streaming.zig b/src/deps/zig-clap/clap/streaming.zig
new file mode 100644
index 000000000..e3948e33b
--- /dev/null
+++ b/src/deps/zig-clap/clap/streaming.zig
@@ -0,0 +1,430 @@
+const builtin = @import("builtin");
+const clap = @import("../clap.zig");
+const std = @import("std");
+
+const args = clap.args;
+const debug = std.debug;
+const heap = std.heap;
+const io = std.io;
+const mem = std.mem;
+const os = std.os;
+const testing = std.testing;
+
+/// The result returned from StreamingClap.next
+pub fn Arg(comptime Id: type) type {
+ return struct {
+ const Self = @This();
+
+ param: *const clap.Param(Id),
+ value: ?[]const u8 = null,
+ };
+}
+
+/// A command line argument parser which, given an ArgIterator, will parse arguments according
+/// to the params. StreamingClap parses in an iterating manner, so you have to use a loop together with
+/// StreamingClap.next to parse all the arguments of your program.
+pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
+ return struct {
+ const State = union(enum) {
+ normal,
+ chaining: Chaining,
+ rest_are_positional,
+
+ const Chaining = struct {
+ arg: []const u8,
+ index: usize,
+ };
+ };
+
+ params: []const clap.Param(Id),
+ iter: *ArgIterator,
+ state: State = .normal,
+ positional: ?*const clap.Param(Id) = null,
+ diagnostic: ?*clap.Diagnostic = null,
+
+ /// Get the next Arg that matches a Param.
+ pub fn next(parser: *@This()) !?Arg(Id) {
+ switch (parser.state) {
+ .normal => return try parser.normal(),
+ .chaining => |state| return try parser.chainging(state),
+ .rest_are_positional => {
+ const param = parser.positionalParam() orelse unreachable;
+ const value = parser.iter.next() orelse return null;
+ return Arg(Id){ .param = param, .value = value };
+ },
+ }
+ }
+
+ fn normal(parser: *@This()) !?Arg(Id) {
+ const ArgType = Arg(Id);
+ const arg_info = (try parser.parseNextArg()) orelse return null;
+ const arg = arg_info.arg;
+
+ switch (arg_info.kind) {
+ .long => {
+ const eql_index = mem.indexOfScalar(u8, arg, '=');
+ const name = if (eql_index) |i| arg[0..i] else arg;
+ const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
+
+ for (parser.params) |*param| {
+ const match = param.names.long orelse continue;
+
+ if (!mem.eql(u8, name, match))
+ continue;
+ if (param.takes_value == .none) {
+ if (maybe_value != null)
+ return parser.err(arg, .{ .long = name }, error.DoesntTakeValue);
+
+ return ArgType{ .param = param };
+ }
+
+ const value = blk: {
+ if (maybe_value) |v|
+ break :blk v;
+
+ break :blk parser.iter.next() orelse brk2: {
+ if (param.takes_value != .one_optional)
+ return parser.err(arg, .{ .long = name }, error.MissingValue);
+
+ break :brk2 "";
+ };
+ };
+
+ return ArgType{ .param = param, .value = value };
+ }
+
+ return null;
+ },
+ .short => return try parser.chainging(.{
+ .arg = arg,
+ .index = 0,
+ }),
+ .positional => if (parser.positionalParam()) |param| {
+ // If we find a positional with the value `--` then we
+ // interpret the rest of the arguments as positional
+ // arguments.
+ if (mem.eql(u8, arg, "--")) {
+ parser.state = .rest_are_positional;
+ const value = parser.iter.next() orelse return null;
+ return Arg(Id){ .param = param, .value = value };
+ }
+
+ return Arg(Id){ .param = param, .value = arg };
+ } else {
+ return parser.err(arg, .{}, error.InvalidArgument);
+ },
+ }
+ }
+
+ fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) {
+ const arg = state.arg;
+ const index = state.index;
+ const next_index = index + 1;
+
+ for (parser.params) |*param| {
+ const short = param.names.short orelse continue;
+ if (short != arg[index])
+ continue;
+
+ // Before we return, we have to set the new state of the clap
+ defer {
+ if (arg.len <= next_index or param.takes_value != .none) {
+ parser.state = .normal;
+ } else {
+ parser.state = .{
+ .chaining = .{
+ .arg = arg,
+ .index = next_index,
+ },
+ };
+ }
+ }
+
+ const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false;
+ if (param.takes_value == .none or param.takes_value == .one_optional) {
+ if (next_is_eql and param.takes_value == .none)
+ return parser.err(arg, .{ .short = short }, error.DoesntTakeValue);
+ return Arg(Id){ .param = param };
+ }
+
+ if (arg.len <= next_index) {
+ const value = parser.iter.next() orelse
+ return parser.err(arg, .{ .short = short }, error.MissingValue);
+
+ return Arg(Id){ .param = param, .value = value };
+ }
+
+ if (next_is_eql)
+ return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] };
+
+ return Arg(Id){ .param = param, .value = arg[next_index..] };
+ }
+
+ return parser.err(arg, .{ .short = arg[index] }, error.InvalidArgument);
+ }
+
+ fn positionalParam(parser: *@This()) ?*const clap.Param(Id) {
+ if (parser.positional) |p|
+ return p;
+
+ for (parser.params) |*param| {
+ if (param.names.long) |_|
+ continue;
+ if (param.names.short) |_|
+ continue;
+
+ parser.positional = param;
+ return param;
+ }
+
+ return null;
+ }
+
+ const ArgInfo = struct {
+ arg: []const u8,
+ kind: enum {
+ long,
+ short,
+ positional,
+ },
+ };
+
+ fn parseNextArg(parser: *@This()) !?ArgInfo {
+ const full_arg = parser.iter.next() orelse return null;
+ if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-"))
+ return ArgInfo{ .arg = full_arg, .kind = .positional };
+ if (mem.startsWith(u8, full_arg, "--"))
+ return ArgInfo{ .arg = full_arg[2..], .kind = .long };
+ if (mem.startsWith(u8, full_arg, "-"))
+ return ArgInfo{ .arg = full_arg[1..], .kind = .short };
+
+ return ArgInfo{ .arg = full_arg, .kind = .positional };
+ }
+
+ fn err(parser: @This(), arg: []const u8, names: clap.Names, _err: anytype) @TypeOf(_err) {
+ if (parser.diagnostic) |d|
+ d.* = .{ .arg = arg, .name = names };
+ return _err;
+ }
+ };
+}
+
+fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void {
+ var iter = args.SliceIterator{ .args = args_strings };
+ var c = StreamingClap(u8, args.SliceIterator){
+ .params = params,
+ .iter = &iter,
+ };
+
+ for (results) |res| {
+ const arg = (c.next() catch unreachable) orelse unreachable;
+ testing.expectEqual(res.param, arg.param);
+ const expected_value = res.value orelse {
+ testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value);
+ continue;
+ };
+ const actual_value = arg.value orelse unreachable;
+ testing.expectEqualSlices(u8, expected_value, actual_value);
+ }
+
+ if (c.next() catch unreachable) |_|
+ unreachable;
+}
+
+fn testErr(params: []const clap.Param(u8), args_strings: []const []const u8, expected: []const u8) void {
+ var diag = clap.Diagnostic{};
+ var iter = args.SliceIterator{ .args = args_strings };
+ var c = StreamingClap(u8, args.SliceIterator){
+ .params = params,
+ .iter = &iter,
+ .diagnostic = &diag,
+ };
+ while (c.next() catch |err| {
+ var buf: [1024]u8 = undefined;
+ var fbs = io.fixedBufferStream(&buf);
+ diag.report(fbs.writer(), err) catch unreachable;
+ testing.expectEqualStrings(expected, fbs.getWritten());
+ return;
+ }) |_| {}
+
+ testing.expect(false);
+}
+
+test "short params" {
+ const params = [_]clap.Param(u8){
+ .{ .id = 0, .names = .{ .short = 'a' } },
+ .{ .id = 1, .names = .{ .short = 'b' } },
+ .{
+ .id = 2,
+ .names = .{ .short = 'c' },
+ .takes_value = .one,
+ },
+ .{
+ .id = 3,
+ .names = .{ .short = 'd' },
+ .takes_value = .many,
+ },
+ };
+
+ const a = &params[0];
+ const b = &params[1];
+ const c = &params[2];
+ const d = &params[3];
+
+ testNoErr(
+ &params,
+ &[_][]const u8{
+ "-a", "-b", "-ab", "-ba",
+ "-c", "0", "-c=0", "-ac",
+ "0", "-ac=0", "-d=0",
+ },
+ &[_]Arg(u8){
+ .{ .param = a },
+ .{ .param = b },
+ .{ .param = a },
+ .{ .param = b },
+ .{ .param = b },
+ .{ .param = a },
+ .{ .param = c, .value = "0" },
+ .{ .param = c, .value = "0" },
+ .{ .param = a },
+ .{ .param = c, .value = "0" },
+ .{ .param = a },
+ .{ .param = c, .value = "0" },
+ .{ .param = d, .value = "0" },
+ },
+ );
+}
+
+test "long params" {
+ const params = [_]clap.Param(u8){
+ .{ .id = 0, .names = .{ .long = "aa" } },
+ .{ .id = 1, .names = .{ .long = "bb" } },
+ .{
+ .id = 2,
+ .names = .{ .long = "cc" },
+ .takes_value = .one,
+ },
+ .{
+ .id = 3,
+ .names = .{ .long = "dd" },
+ .takes_value = .many,
+ },
+ };
+
+ const aa = &params[0];
+ const bb = &params[1];
+ const cc = &params[2];
+ const dd = &params[3];
+
+ testNoErr(
+ &params,
+ &[_][]const u8{
+ "--aa", "--bb",
+ "--cc", "0",
+ "--cc=0", "--dd=0",
+ },
+ &[_]Arg(u8){
+ .{ .param = aa },
+ .{ .param = bb },
+ .{ .param = cc, .value = "0" },
+ .{ .param = cc, .value = "0" },
+ .{ .param = dd, .value = "0" },
+ },
+ );
+}
+
+test "positional params" {
+ const params = [_]clap.Param(u8){.{
+ .id = 0,
+ .takes_value = .one,
+ }};
+
+ testNoErr(
+ &params,
+ &[_][]const u8{ "aa", "bb" },
+ &[_]Arg(u8){
+ .{ .param = &params[0], .value = "aa" },
+ .{ .param = &params[0], .value = "bb" },
+ },
+ );
+}
+
+test "all params" {
+ const params = [_]clap.Param(u8){
+ .{
+ .id = 0,
+ .names = .{ .short = 'a', .long = "aa" },
+ },
+ .{
+ .id = 1,
+ .names = .{ .short = 'b', .long = "bb" },
+ },
+ .{
+ .id = 2,
+ .names = .{ .short = 'c', .long = "cc" },
+ .takes_value = .one,
+ },
+ .{ .id = 3, .takes_value = .one },
+ };
+
+ const aa = &params[0];
+ const bb = &params[1];
+ const cc = &params[2];
+ const positional = &params[3];
+
+ testNoErr(
+ &params,
+ &[_][]const u8{
+ "-a", "-b", "-ab", "-ba",
+ "-c", "0", "-c=0", "-ac",
+ "0", "-ac=0", "--aa", "--bb",
+ "--cc", "0", "--cc=0", "something",
+ "-", "--", "--cc=0", "-a",
+ },
+ &[_]Arg(u8){
+ .{ .param = aa },
+ .{ .param = bb },
+ .{ .param = aa },
+ .{ .param = bb },
+ .{ .param = bb },
+ .{ .param = aa },
+ .{ .param = cc, .value = "0" },
+ .{ .param = cc, .value = "0" },
+ .{ .param = aa },
+ .{ .param = cc, .value = "0" },
+ .{ .param = aa },
+ .{ .param = cc, .value = "0" },
+ .{ .param = aa },
+ .{ .param = bb },
+ .{ .param = cc, .value = "0" },
+ .{ .param = cc, .value = "0" },
+ .{ .param = positional, .value = "something" },
+ .{ .param = positional, .value = "-" },
+ .{ .param = positional, .value = "--cc=0" },
+ .{ .param = positional, .value = "-a" },
+ },
+ );
+}
+
+test "errors" {
+ const params = [_]clap.Param(u8){
+ .{
+ .id = 0,
+ .names = .{ .short = 'a', .long = "aa" },
+ },
+ .{
+ .id = 1,
+ .names = .{ .short = 'c', .long = "cc" },
+ .takes_value = .one,
+ },
+ };
+ testErr(&params, &[_][]const u8{"q"}, "Invalid argument 'q'\n");
+ testErr(&params, &[_][]const u8{"-q"}, "Invalid argument '-q'\n");
+ testErr(&params, &[_][]const u8{"--q"}, "Invalid argument '--q'\n");
+ testErr(&params, &[_][]const u8{"--q=1"}, "Invalid argument '--q'\n");
+ testErr(&params, &[_][]const u8{"-a=1"}, "The argument '-a' does not take a value\n");
+ testErr(&params, &[_][]const u8{"--aa=1"}, "The argument '--aa' does not take a value\n");
+ testErr(&params, &[_][]const u8{"-c"}, "The argument '-c' requires a value but none was supplied\n");
+ testErr(&params, &[_][]const u8{"--cc"}, "The argument '--cc' requires a value but none was supplied\n");
+}
diff --git a/src/deps/zig-clap/gyro.zzz b/src/deps/zig-clap/gyro.zzz
new file mode 100644
index 000000000..3853db049
--- /dev/null
+++ b/src/deps/zig-clap/gyro.zzz
@@ -0,0 +1,14 @@
+pkgs:
+ clap:
+ version: 0.3.0
+ license: Unlicense
+ description: Simple command line argument parsing library
+ source_url: "https://github.com/Hejsil/zig-clap"
+ root: clap.zig
+ files:
+ README.md
+ LICENSE
+ build.zig
+ clap/*.zig
+ example/*.zig
+
diff --git a/src/deps/zig-clap/zig.mod b/src/deps/zig-clap/zig.mod
new file mode 100644
index 000000000..00c1a690d
--- /dev/null
+++ b/src/deps/zig-clap/zig.mod
@@ -0,0 +1,5 @@
+id: aoe2l16htluewam6bfwvv0khsbbno8g8jd7suonifg74u7kd
+name: clap
+main: clap.zig
+license: Unlicense
+dependencies: