diff --git a/README.md b/README.md index 094e582..d15063a 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,9 @@ pub fn main() !void { var res = clap.parse(clap.Help, ¶ms, parsers, .{ .diagnostic = &diag, .allocator = gpa.allocator(), + // The assignment separator can be configured. `--number=1` and `--number:1` is now + // allowed. + .assignment_separators = "=:", }) catch |err| { diag.report(io.getStdErr().writer(), err) catch {}; return err; diff --git a/clap.zig b/clap.zig index b603222..6054b24 100644 --- a/clap.zig +++ b/clap.zig @@ -18,6 +18,8 @@ test "clap" { testing.refAllDecls(@This()); } +pub const default_assignment_separators = "="; + /// The names a `Param` can have. pub const Names = struct { /// '-' prefix @@ -643,6 +645,7 @@ test "Diagnostic.report" { pub const ParseOptions = struct { allocator: mem.Allocator, diagnostic: ?*Diagnostic = null, + assignment_separators: []const u8 = default_assignment_separators, }; /// Same as `parseEx` but uses the `args.OsIterator` by default. @@ -662,6 +665,7 @@ pub fn parse( // Let's reuse the arena from the `OSIterator` since we already have it. .allocator = arena.allocator(), .diagnostic = opt.diagnostic, + .assignment_separators = opt.assignment_separators, }); return Result(Id, params, value_parsers){ @@ -733,6 +737,7 @@ pub fn parseEx( .params = params, .iter = iter, .diagnostic = opt.diagnostic, + .assignment_separators = opt.assignment_separators, }; while (try stream.next()) |arg| { // TODO: We cannot use `try` inside the inline for because of a compiler bug that @@ -955,6 +960,24 @@ test "str and u64" { defer res.deinit(); } +test "different assignment separators" { + const params = comptime parseParamsComptime( + \\-a, --aa ... + \\ + ); + + var iter = args.SliceIterator{ + .args = &.{ "-a=0", "--aa=1", "-a:2", "--aa:3" }, + }; + var res = try parseEx(Help, ¶ms, parsers.default, &iter, .{ + .allocator = testing.allocator, + .assignment_separators = "=:", + }); + defer res.deinit(); + + try testing.expectEqualSlices(usize, &.{ 0, 1, 2, 3 }, res.args.aa); +} + test "everything" { const params = comptime parseParamsComptime( \\-a, --aa diff --git a/clap/streaming.zig b/clap/streaming.zig index 2ab9c8d..eba84bb 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig @@ -44,11 +44,14 @@ pub fn Clap(comptime Id: type, comptime ArgIterator: type) type { }; }; + state: State = .normal, + params: []const clap.Param(Id), iter: *ArgIterator, - state: State = .normal, + positional: ?*const clap.Param(Id) = null, diagnostic: ?*clap.Diagnostic = null, + assignment_separators: []const u8 = clap.default_assignment_separators, /// Get the next Arg that matches a Param. pub fn next(parser: *@This()) !?Arg(Id) { @@ -68,7 +71,7 @@ pub fn Clap(comptime Id: type, comptime ArgIterator: type) type { const arg = arg_info.arg; switch (arg_info.kind) { .long => { - const eql_index = mem.indexOfScalar(u8, arg, '='); + const eql_index = mem.indexOfAny(u8, arg, parser.assignment_separators); const name = if (eql_index) |i| arg[0..i] else arg; const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; @@ -142,9 +145,13 @@ pub fn Clap(comptime Id: type, comptime ArgIterator: type) type { } } - const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false; + const next_is_separator = if (next_index < arg.len) + std.mem.indexOfScalar(u8, parser.assignment_separators, arg[next_index]) != null + else + false; + if (param.takes_value == .none) { - if (next_is_eql) + if (next_is_separator) return parser.err(arg, .{ .short = short }, Error.DoesntTakeValue); return Arg(Id){ .param = param }; } @@ -156,7 +163,7 @@ pub fn Clap(comptime Id: type, comptime ArgIterator: type) type { return Arg(Id){ .param = param, .value = value }; } - if (next_is_eql) + if (next_is_separator) return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] }; return Arg(Id){ .param = param, .value = arg[next_index..] }; @@ -211,19 +218,12 @@ pub fn Clap(comptime Id: type, comptime ArgIterator: type) type { }; } -fn testNoErr( - params: []const clap.Param(u8), - args_strings: []const []const u8, +fn expectArgs( + parser: *Clap(u8, args.SliceIterator), results: []const Arg(u8), ) !void { - var iter = args.SliceIterator{ .args = args_strings }; - var c = Clap(u8, args.SliceIterator){ - .params = params, - .iter = &iter, - }; - for (results) |res| { - const arg = (try c.next()) orelse return error.TestFailed; + const arg = (try parser.next()) orelse return error.TestFailed; try testing.expectEqual(res.param, arg.param); const expected_value = res.value orelse { try testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value); @@ -233,23 +233,18 @@ fn testNoErr( try testing.expectEqualSlices(u8, expected_value, actual_value); } - if (try c.next()) |_| + if (try parser.next()) |_| return error.TestFailed; } -fn testErr( - params: []const clap.Param(u8), - args_strings: []const []const u8, +fn expectError( + parser: *Clap(u8, args.SliceIterator), expected: []const u8, ) !void { - var diag: clap.Diagnostic = undefined; - var iter = args.SliceIterator{ .args = args_strings }; - var c = Clap(u8, args.SliceIterator){ - .params = params, - .iter = &iter, - .diagnostic = &diag, - }; - while (c.next() catch |err| { + var diag: clap.Diagnostic = .{}; + parser.diagnostic = &diag; + + while (parser.next() catch |err| { var buf: [1024]u8 = undefined; var fbs = io.fixedBufferStream(&buf); diag.report(fbs.writer(), err) catch return error.TestFailed; @@ -281,29 +276,28 @@ test "short params" { const c = ¶ms[2]; const d = ¶ms[3]; - try testNoErr( - ¶ms, - &.{ - "-a", "-b", "-ab", "-ba", - "-c", "0", "-c=0", "-ac", - "0", "-ac=0", "-d=0", - }, - &.{ - .{ .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" }, - }, - ); + var iter = args.SliceIterator{ .args = &.{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", "-ac", + "0", "-ac=0", "-d=0", + } }; + var parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + + try expectArgs(&parser, &.{ + .{ .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" { @@ -327,21 +321,20 @@ test "long params" { const cc = ¶ms[2]; const dd = ¶ms[3]; - try testNoErr( - ¶ms, - &.{ - "--aa", "--bb", - "--cc", "0", - "--cc=0", "--dd=0", - }, - &.{ - .{ .param = aa }, - .{ .param = bb }, - .{ .param = cc, .value = "0" }, - .{ .param = cc, .value = "0" }, - .{ .param = dd, .value = "0" }, - }, - ); + var iter = args.SliceIterator{ .args = &.{ + "--aa", "--bb", + "--cc", "0", + "--cc=0", "--dd=0", + } }; + var parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + + try expectArgs(&parser, &.{ + .{ .param = aa }, + .{ .param = bb }, + .{ .param = cc, .value = "0" }, + .{ .param = cc, .value = "0" }, + .{ .param = dd, .value = "0" }, + }); } test "positional params" { @@ -350,14 +343,16 @@ test "positional params" { .takes_value = .one, }}; - try testNoErr( - ¶ms, - &.{ "aa", "bb" }, - &.{ - .{ .param = ¶ms[0], .value = "aa" }, - .{ .param = ¶ms[0], .value = "bb" }, - }, - ); + var iter = args.SliceIterator{ .args = &.{ + "aa", + "bb", + } }; + var parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + + try expectArgs(&parser, &.{ + .{ .param = ¶ms[0], .value = "aa" }, + .{ .param = ¶ms[0], .value = "bb" }, + }); } test "all params" { @@ -383,38 +378,66 @@ test "all params" { const cc = ¶ms[2]; const positional = ¶ms[3]; - try testNoErr( - ¶ms, - &.{ - "-a", "-b", "-ab", "-ba", - "-c", "0", "-c=0", "-ac", - "0", "-ac=0", "--aa", "--bb", - "--cc", "0", "--cc=0", "something", - "-", "--", "--cc=0", "-a", - }, - &.{ - .{ .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" }, + var iter = args.SliceIterator{ .args = &.{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", "-ac", + "0", "-ac=0", "--aa", "--bb", + "--cc", "0", "--cc=0", "something", + "-", "--", "--cc=0", "-a", + } }; + var parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + + try expectArgs(&parser, &.{ + .{ .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 "different assignment separators" { + const params = [_]clap.Param(u8){ + .{ + .id = 0, + .names = .{ .short = 'a', .long = "aa" }, + .takes_value = .one, }, - ); + }; + + const aa = ¶ms[0]; + + var iter = args.SliceIterator{ .args = &.{ + "-a=0", "--aa=0", + "-a:0", "--aa:0", + } }; + var parser = Clap(u8, args.SliceIterator){ + .params = ¶ms, + .iter = &iter, + .assignment_separators = "=:", + }; + + try expectArgs(&parser, &.{ + .{ .param = aa, .value = "0" }, + .{ .param = aa, .value = "0" }, + .{ .param = aa, .value = "0" }, + .{ .param = aa, .value = "0" }, + }); } test "errors" { @@ -429,16 +452,36 @@ test "errors" { .takes_value = .one, }, }; - try testErr(¶ms, &.{"q"}, "Invalid argument 'q'\n"); - try testErr(¶ms, &.{"-q"}, "Invalid argument '-q'\n"); - try testErr(¶ms, &.{"--q"}, "Invalid argument '--q'\n"); - try testErr(¶ms, &.{"--q=1"}, "Invalid argument '--q'\n"); - try testErr(¶ms, &.{"-a=1"}, "The argument '-a' does not take a value\n"); - try testErr(¶ms, &.{"--aa=1"}, "The argument '--aa' does not take a value\n"); - try testErr(¶ms, &.{"-c"}, "The argument '-c' requires a value but none was supplied\n"); - try testErr( - ¶ms, - &.{"--cc"}, - "The argument '--cc' requires a value but none was supplied\n", - ); + + var iter = args.SliceIterator{ .args = &.{"q"} }; + var parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "Invalid argument 'q'\n"); + + iter = args.SliceIterator{ .args = &.{"-q"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "Invalid argument '-q'\n"); + + iter = args.SliceIterator{ .args = &.{"--q"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "Invalid argument '--q'\n"); + + iter = args.SliceIterator{ .args = &.{"--q=1"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "Invalid argument '--q'\n"); + + iter = args.SliceIterator{ .args = &.{"-a=1"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "The argument '-a' does not take a value\n"); + + iter = args.SliceIterator{ .args = &.{"--aa=1"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "The argument '--aa' does not take a value\n"); + + iter = args.SliceIterator{ .args = &.{"-c"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "The argument '-c' requires a value but none was supplied\n"); + + iter = args.SliceIterator{ .args = &.{"--cc"} }; + parser = Clap(u8, args.SliceIterator){ .params = ¶ms, .iter = &iter }; + try expectError(&parser, "The argument '--cc' requires a value but none was supplied\n"); } diff --git a/example/simple-ex.zig b/example/simple-ex.zig index 436d058..2943f4e 100644 --- a/example/simple-ex.zig +++ b/example/simple-ex.zig @@ -34,6 +34,9 @@ pub fn main() !void { var res = clap.parse(clap.Help, ¶ms, parsers, .{ .diagnostic = &diag, .allocator = gpa.allocator(), + // The assignment separator can be configured. `--number=1` and `--number:1` is now + // allowed. + .assignment_separators = "=:", }) catch |err| { diag.report(io.getStdErr().writer(), err) catch {}; return err;