const builtin = @import("builtin"); const clap = @import("../clap.zig"); const std = @import("std"); const args = clap.args; const testing = std.testing; const heap = std.heap; const mem = std.mem; const os = std.os; /// 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, const Chaining = struct { arg: []const u8, index: usize, }; }; params: []const clap.Param(Id), iter: *ArgIterator, state: State = State.Normal, /// Get the next ::Arg that matches a ::Param. pub fn next(parser: *@This()) !?Arg(Id) { const ArgInfo = struct { const Kind = enum { Long, Short, Positional, }; arg: []const u8, kind: Kind, }; switch (parser.state) { .Normal => { const full_arg = (try parser.iter.next()) orelse return null; const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) ArgInfo{ .arg = full_arg, .kind = .Positional } else if (mem.startsWith(u8, full_arg, "--")) ArgInfo{ .arg = full_arg[2..], .kind = .Long } else if (mem.startsWith(u8, full_arg, "-")) ArgInfo{ .arg = full_arg[1..], .kind = .Short } else ArgInfo{ .arg = full_arg, .kind = .Positional }; const arg = arg_info.arg; const kind = arg_info.kind; const eql_index = mem.indexOfScalar(u8, arg, '='); switch (kind) { ArgInfo.Kind.Long => { for (parser.params) |*param| { const match = param.names.long orelse continue; const name = if (eql_index) |i| arg[0..i] else arg; const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; if (!mem.eql(u8, name, match)) continue; if (!param.takes_value) { if (maybe_value != null) return error.DoesntTakeValue; return Arg(Id){ .param = param }; } const value = blk: { if (maybe_value) |v| break :blk v; break :blk (try parser.iter.next()) orelse return error.MissingValue; }; return Arg(Id){ .param = param, .value = value }; } }, ArgInfo.Kind.Short => { return try parser.chainging(State.Chaining{ .arg = full_arg, .index = (full_arg.len - arg.len), }); }, ArgInfo.Kind.Positional => { for (parser.params) |*param| { if (param.names.long) |_| continue; if (param.names.short) |_| continue; return Arg(Id){ .param = param, .value = arg }; } }, } return error.InvalidArgument; }, .Chaining => |state| return try parser.chainging(state), } } 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) { parser.state = State.Normal; } else { parser.state = State{ .Chaining = State.Chaining{ .arg = arg, .index = next_index, }, }; } } if (!param.takes_value) return Arg(Id){ .param = param }; if (arg.len <= next_index) { const value = (try parser.iter.next()) orelse return error.MissingValue; return Arg(Id){ .param = param, .value = value }; } if (arg[next_index] == '=') return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] }; return Arg(Id){ .param = param, .value = arg[next_index..] }; } return error.InvalidArgument; } }; } 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; } test "clap.streaming.StreamingClap: short params" { const params = [_]clap.Param(u8){ clap.Param(u8){ .id = 0, .names = clap.Names{ .short = 'a' }, }, clap.Param(u8){ .id = 1, .names = clap.Names{ .short = 'b' }, }, clap.Param(u8){ .id = 2, .names = clap.Names{ .short = 'c' }, .takes_value = true, }, }; const a = ¶ms[0]; const b = ¶ms[1]; const c = ¶ms[2]; testNoErr( ¶ms, &[_][]const u8{ "-a", "-b", "-ab", "-ba", "-c", "0", "-c=0", "-ac", "0", "-ac=0", }, &[_]Arg(u8){ Arg(u8){ .param = a }, Arg(u8){ .param = b }, Arg(u8){ .param = a }, Arg(u8){ .param = b }, Arg(u8){ .param = b }, Arg(u8){ .param = a }, Arg(u8){ .param = c, .value = "0" }, Arg(u8){ .param = c, .value = "0" }, Arg(u8){ .param = a }, Arg(u8){ .param = c, .value = "0" }, Arg(u8){ .param = a }, Arg(u8){ .param = c, .value = "0" }, }, ); } test "clap.streaming.StreamingClap: long params" { const params = [_]clap.Param(u8){ clap.Param(u8){ .id = 0, .names = clap.Names{ .long = "aa" }, }, clap.Param(u8){ .id = 1, .names = clap.Names{ .long = "bb" }, }, clap.Param(u8){ .id = 2, .names = clap.Names{ .long = "cc" }, .takes_value = true, }, }; const aa = ¶ms[0]; const bb = ¶ms[1]; const cc = ¶ms[2]; testNoErr( ¶ms, &[_][]const u8{ "--aa", "--bb", "--cc", "0", "--cc=0", }, &[_]Arg(u8){ Arg(u8){ .param = aa }, Arg(u8){ .param = bb }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = cc, .value = "0" }, }, ); } test "clap.streaming.StreamingClap: positional params" { const params = [_]clap.Param(u8){clap.Param(u8){ .id = 0, .takes_value = true, }}; testNoErr( ¶ms, &[_][]const u8{ "aa", "bb" }, &[_]Arg(u8){ Arg(u8){ .param = ¶ms[0], .value = "aa" }, Arg(u8){ .param = ¶ms[0], .value = "bb" }, }, ); } test "clap.streaming.StreamingClap: all params" { const params = [_]clap.Param(u8){ clap.Param(u8){ .id = 0, .names = clap.Names{ .short = 'a', .long = "aa", }, }, clap.Param(u8){ .id = 1, .names = clap.Names{ .short = 'b', .long = "bb", }, }, clap.Param(u8){ .id = 2, .names = clap.Names{ .short = 'c', .long = "cc", }, .takes_value = true, }, clap.Param(u8){ .id = 3, .takes_value = true, }, }; const aa = ¶ms[0]; const bb = ¶ms[1]; const cc = ¶ms[2]; const positional = ¶ms[3]; testNoErr( ¶ms, &[_][]const u8{ "-a", "-b", "-ab", "-ba", "-c", "0", "-c=0", "-ac", "0", "-ac=0", "--aa", "--bb", "--cc", "0", "--cc=0", "something", "--", "-", }, &[_]Arg(u8){ Arg(u8){ .param = aa }, Arg(u8){ .param = bb }, Arg(u8){ .param = aa }, Arg(u8){ .param = bb }, Arg(u8){ .param = bb }, Arg(u8){ .param = aa }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = aa }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = aa }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = aa }, Arg(u8){ .param = bb }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = positional, .value = "something" }, Arg(u8){ .param = positional, .value = "--" }, Arg(u8){ .param = positional, .value = "-" }, }, ); }