From f3eb797336c1bbdae391657ef6ac54e4015b5fde Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sun, 6 Oct 2019 17:20:56 +0200 Subject: fmt, mv src/ clap/ and run fmt on build --- build.zig | 5 + clap.zig | 17 ++- clap/args.zig | 71 +++++++++++ clap/comptime.zig | 143 ++++++++++++++++++++++ clap/streaming.zig | 348 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/args.zig | 71 ----------- src/comptime.zig | 143 ---------------------- src/streaming.zig | 348 ----------------------------------------------------- 8 files changed, 575 insertions(+), 571 deletions(-) create mode 100644 clap/args.zig create mode 100644 clap/comptime.zig create mode 100644 clap/streaming.zig delete mode 100644 src/args.zig delete mode 100644 src/comptime.zig delete mode 100644 src/streaming.zig diff --git a/build.zig b/build.zig index 104e7e3..9d2ef7b 100644 --- a/build.zig +++ b/build.zig @@ -7,6 +7,11 @@ const Builder = std.build.Builder; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const fmt_step = b.addFmt([_][]const u8{ + "build.zig", + "clap", + }); + 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); diff --git a/clap.zig b/clap.zig index ac7f6c1..1a6a95d 100644 --- a/clap.zig +++ b/clap.zig @@ -5,7 +5,7 @@ const io = std.io; const mem = std.mem; const testing = std.testing; -pub const args = @import("src/args.zig"); +pub const args = @import("clap/args.zig"); test "clap" { _ = args; @@ -13,8 +13,8 @@ test "clap" { _ = StreamingClap; } -pub const ComptimeClap = @import("src/comptime.zig").ComptimeClap; -pub const StreamingClap = @import("src/streaming.zig").StreamingClap; +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 { @@ -69,7 +69,7 @@ pub fn parseParam(line: []const u8) !Param(Help) { if (!mem.startsWith(u8, param_str, "--") and mem.startsWith(u8, param_str, "-")) { const found_comma = param_str[param_str.len - 1] == ','; if (found_comma) - param_str = param_str[0..param_str.len - 1]; + param_str = param_str[0 .. param_str.len - 1]; if (param_str.len != 2) return error.InvalidShortParam; @@ -83,7 +83,7 @@ pub fn parseParam(line: []const u8) !Param(Help) { const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk; res.id.value = help_msg[start..][0..len]; res.takes_value = true; - help_msg = help_msg[start + len + 1..]; + help_msg = help_msg[start + len + 1 ..]; } } @@ -99,7 +99,7 @@ pub fn parseParam(line: []const u8) !Param(Help) { if (param_str[param_str.len - 1] == ',') return error.TrailingComma; - + var help_msg = it.rest(); if (it.next()) |next| blk: { if (mem.startsWith(u8, next, "<")) { @@ -107,10 +107,10 @@ pub fn parseParam(line: []const u8) !Param(Help) { const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk; res.id.value = help_msg[start..][0..len]; res.takes_value = true; - help_msg = help_msg[start + len + 1..]; + help_msg = help_msg[start + len + 1 ..]; } } - + res.id.msg = mem.trim(u8, help_msg, " \t"); return res; } @@ -223,7 +223,6 @@ fn find(str: []const u8, f: []const u8) []const u8 { return str[i..][0..f.len]; } - /// Will print a help message in the following format: /// -s, --long help_text /// -s, help_text diff --git a/clap/args.zig b/clap/args.zig new file mode 100644 index 0000000..4234ada --- /dev/null +++ b/clap/args.zig @@ -0,0 +1,71 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const debug = std.debug; +const heap = std.heap; +const mem = std.mem; +const process = std.process; + +/// An example of what methods should be implemented on an arg iterator. +pub const ExampleArgIterator = struct { + const Error = error{}; + + pub fn next(iter: *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 "clap.args.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.NextError; + + arena: heap.ArenaAllocator, + args: process.ArgIterator, + + pub fn init(allocator: *mem.Allocator) OsIterator { + return OsIterator{ + .arena = heap.ArenaAllocator.init(allocator), + .args = process.args(), + }; + } + + pub fn deinit(iter: *OsIterator) void { + iter.arena.deinit(); + } + + pub fn next(iter: *OsIterator) Error!?[]const u8 { + if (builtin.os == builtin.Os.windows) { + return try iter.args.next(&iter.arena.allocator) orelse return null; + } else { + return iter.args.nextPosix(); + } + } +}; diff --git a/clap/comptime.zig b/clap/comptime.zig new file mode 100644 index 0000000..f5c2762 --- /dev/null +++ b/clap/comptime.zig @@ -0,0 +1,143 @@ +const clap = @import("../clap.zig"); +const std = @import("std"); + +const testing = std.testing; +const heap = std.heap; +const mem = std.mem; +const debug = std.debug; + +pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type { + var flags: usize = 0; + var 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 = if (param.takes_value) &options else &flags; + 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}; + } + + return struct { + options: [options]?[]const u8, + flags: [flags]bool, + pos: []const []const u8, + allocator: *mem.Allocator, + + pub fn parse(allocator: *mem.Allocator, comptime ArgIter: type, iter: *ArgIter) !@This() { + var pos = std.ArrayList([]const u8).init(allocator); + var res = @This(){ + .options = [_]?[]const u8{null} ** options, + .flags = [_]bool{false} ** flags, + .pos = undefined, + .allocator = allocator, + }; + + var stream = clap.StreamingClap(usize, ArgIter){ + .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.?); + } else if (param.takes_value) { + // If we don't have any optional parameters, then this code should + // never be reached. + debug.assert(res.options.len != 0); + + // Hack: Utilize Zigs lazy analyzis to avoid a compiler error + if (res.options.len != 0) + res.options[param.id] = arg.value.?; + } else { + debug.assert(res.flags.len != 0); + if (res.flags.len != 0) + res.flags[param.id] = true; + } + } + + res.pos = pos.toOwnedSlice(); + return res; + } + + pub fn deinit(parser: *@This()) void { + parser.allocator.free(parser.pos); + parser.* = undefined; + } + + pub fn flag(parser: @This(), comptime name: []const u8) bool { + const param = comptime findParam(name); + if (param.takes_value) + @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) + @compileError(name ++ " is a flag and not an option."); + + return parser.options[param.id]; + } + + pub fn positionals(parser: @This()) []const []const u8 { + return parser.pos; + } + + 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."); + } + } + }; +} + +test "clap.comptime.ComptimeClap" { + const Clap = ComptimeClap(clap.Help, comptime [_]clap.Param(clap.Help){ + clap.parseParam("-a, --aa ") catch unreachable, + clap.parseParam("-b, --bb ") catch unreachable, + clap.parseParam("-c, --cc ") catch unreachable, + clap.Param(clap.Help){ + .takes_value = true, + }, + }); + + var buf: [1024]u8 = undefined; + var fb_allocator = heap.FixedBufferAllocator.init(buf[0..]); + var iter = clap.args.SliceIterator{ + .args = [_][]const u8{ + "-a", "-c", "0", "something", + }, + }; + var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator, &iter); + defer args.deinit(); + + testing.expect(args.flag("-a")); + testing.expect(args.flag("--aa")); + testing.expect(!args.flag("-b")); + testing.expect(!args.flag("--bb")); + testing.expectEqualSlices(u8, "0", args.option("-c").?); + testing.expectEqualSlices(u8, "0", args.option("--cc").?); + testing.expectEqual(usize(1), args.positionals().len); + testing.expectEqualSlices(u8, "something", args.positionals()[0]); +} diff --git a/clap/streaming.zig b/clap/streaming.zig new file mode 100644 index 0000000..fa7ce80 --- /dev/null +++ b/clap/streaming.zig @@ -0,0 +1,348 @@ +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(@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( + params, + [_][]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( + params, + [_][]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( + params, + [_][]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( + params, + [_][]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 = "-" }, + }, + ); +} diff --git a/src/args.zig b/src/args.zig deleted file mode 100644 index 4234ada..0000000 --- a/src/args.zig +++ /dev/null @@ -1,71 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("std"); - -const debug = std.debug; -const heap = std.heap; -const mem = std.mem; -const process = std.process; - -/// An example of what methods should be implemented on an arg iterator. -pub const ExampleArgIterator = struct { - const Error = error{}; - - pub fn next(iter: *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 "clap.args.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.NextError; - - arena: heap.ArenaAllocator, - args: process.ArgIterator, - - pub fn init(allocator: *mem.Allocator) OsIterator { - return OsIterator{ - .arena = heap.ArenaAllocator.init(allocator), - .args = process.args(), - }; - } - - pub fn deinit(iter: *OsIterator) void { - iter.arena.deinit(); - } - - pub fn next(iter: *OsIterator) Error!?[]const u8 { - if (builtin.os == builtin.Os.windows) { - return try iter.args.next(&iter.arena.allocator) orelse return null; - } else { - return iter.args.nextPosix(); - } - } -}; diff --git a/src/comptime.zig b/src/comptime.zig deleted file mode 100644 index f5c2762..0000000 --- a/src/comptime.zig +++ /dev/null @@ -1,143 +0,0 @@ -const clap = @import("../clap.zig"); -const std = @import("std"); - -const testing = std.testing; -const heap = std.heap; -const mem = std.mem; -const debug = std.debug; - -pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type { - var flags: usize = 0; - var 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 = if (param.takes_value) &options else &flags; - 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}; - } - - return struct { - options: [options]?[]const u8, - flags: [flags]bool, - pos: []const []const u8, - allocator: *mem.Allocator, - - pub fn parse(allocator: *mem.Allocator, comptime ArgIter: type, iter: *ArgIter) !@This() { - var pos = std.ArrayList([]const u8).init(allocator); - var res = @This(){ - .options = [_]?[]const u8{null} ** options, - .flags = [_]bool{false} ** flags, - .pos = undefined, - .allocator = allocator, - }; - - var stream = clap.StreamingClap(usize, ArgIter){ - .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.?); - } else if (param.takes_value) { - // If we don't have any optional parameters, then this code should - // never be reached. - debug.assert(res.options.len != 0); - - // Hack: Utilize Zigs lazy analyzis to avoid a compiler error - if (res.options.len != 0) - res.options[param.id] = arg.value.?; - } else { - debug.assert(res.flags.len != 0); - if (res.flags.len != 0) - res.flags[param.id] = true; - } - } - - res.pos = pos.toOwnedSlice(); - return res; - } - - pub fn deinit(parser: *@This()) void { - parser.allocator.free(parser.pos); - parser.* = undefined; - } - - pub fn flag(parser: @This(), comptime name: []const u8) bool { - const param = comptime findParam(name); - if (param.takes_value) - @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) - @compileError(name ++ " is a flag and not an option."); - - return parser.options[param.id]; - } - - pub fn positionals(parser: @This()) []const []const u8 { - return parser.pos; - } - - 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."); - } - } - }; -} - -test "clap.comptime.ComptimeClap" { - const Clap = ComptimeClap(clap.Help, comptime [_]clap.Param(clap.Help){ - clap.parseParam("-a, --aa ") catch unreachable, - clap.parseParam("-b, --bb ") catch unreachable, - clap.parseParam("-c, --cc ") catch unreachable, - clap.Param(clap.Help){ - .takes_value = true, - }, - }); - - var buf: [1024]u8 = undefined; - var fb_allocator = heap.FixedBufferAllocator.init(buf[0..]); - var iter = clap.args.SliceIterator{ - .args = [_][]const u8{ - "-a", "-c", "0", "something", - }, - }; - var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator, &iter); - defer args.deinit(); - - testing.expect(args.flag("-a")); - testing.expect(args.flag("--aa")); - testing.expect(!args.flag("-b")); - testing.expect(!args.flag("--bb")); - testing.expectEqualSlices(u8, "0", args.option("-c").?); - testing.expectEqualSlices(u8, "0", args.option("--cc").?); - testing.expectEqual(usize(1), args.positionals().len); - testing.expectEqualSlices(u8, "something", args.positionals()[0]); -} diff --git a/src/streaming.zig b/src/streaming.zig deleted file mode 100644 index b5c3498..0000000 --- a/src/streaming.zig +++ /dev/null @@ -1,348 +0,0 @@ -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(@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( - params, - [_][]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( - params, - [_][]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( - params, - [_][]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( - params, - [_][]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 = "-" }, - }, - ); -} -- cgit v1.2.3