diff options
| -rw-r--r-- | build.zig | 42 | ||||
| -rw-r--r-- | clap.zig (renamed from src/core.zig) | 0 | ||||
| -rw-r--r-- | index.zig | 2 | ||||
| -rw-r--r-- | src/extended.zig | 233 | ||||
| -rw-r--r-- | test.zig | 198 | ||||
| -rw-r--r-- | tests/core.zig | 120 | ||||
| -rw-r--r-- | tests/extended.zig | 306 |
7 files changed, 198 insertions, 703 deletions
diff --git a/build.zig b/build.zig deleted file mode 100644 index 6ec8837..0000000 --- a/build.zig +++ /dev/null | |||
| @@ -1,42 +0,0 @@ | |||
| 1 | const Builder = @import("std").build.Builder; | ||
| 2 | |||
| 3 | pub fn build(b: *Builder) void { | ||
| 4 | const mode = b.standardReleaseOptions(); | ||
| 5 | |||
| 6 | { | ||
| 7 | const example_step = b.step("examples", "Build all examples"); | ||
| 8 | const examples = [][]const u8{}; | ||
| 9 | |||
| 10 | b.default_step.dependOn(example_step); | ||
| 11 | inline for (examples) |example| { | ||
| 12 | comptime const path = "examples/" ++ example ++ ".zig"; | ||
| 13 | const exe = b.addExecutable(example, path); | ||
| 14 | exe.setBuildMode(mode); | ||
| 15 | exe.addPackagePath("clap", "index.zig"); | ||
| 16 | |||
| 17 | const step = b.step("build-" ++ example, "Build '" ++ path ++ "'"); | ||
| 18 | step.dependOn(&exe.step); | ||
| 19 | example_step.dependOn(step); | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | { | ||
| 24 | const test_step = b.step("tests", "Run all tests"); | ||
| 25 | const tests = [][]const u8{ | ||
| 26 | "core", | ||
| 27 | "extended", | ||
| 28 | }; | ||
| 29 | |||
| 30 | b.default_step.dependOn(test_step); | ||
| 31 | inline for (tests) |test_name| { | ||
| 32 | comptime const path = "tests/" ++ test_name ++ ".zig"; | ||
| 33 | const t = b.addTest(path); | ||
| 34 | t.setBuildMode(mode); | ||
| 35 | //t.addPackagePath("clap", "index.zig"); | ||
| 36 | |||
| 37 | const step = b.step("test-" ++ test_name, "Run test '" ++ test_name ++ "'"); | ||
| 38 | step.dependOn(&t.step); | ||
| 39 | test_step.dependOn(step); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/index.zig b/index.zig deleted file mode 100644 index 805d72f..0000000 --- a/index.zig +++ /dev/null | |||
| @@ -1,2 +0,0 @@ | |||
| 1 | pub const core = @import("src/core.zig"); | ||
| 2 | pub const extended = @import("src/extended.zig"); | ||
diff --git a/src/extended.zig b/src/extended.zig deleted file mode 100644 index f7fc87d..0000000 --- a/src/extended.zig +++ /dev/null | |||
| @@ -1,233 +0,0 @@ | |||
| 1 | pub const core = @import("core.zig"); | ||
| 2 | |||
| 3 | const builtin = @import("builtin"); | ||
| 4 | const std = @import("std"); | ||
| 5 | |||
| 6 | const mem = std.mem; | ||
| 7 | const fmt = std.fmt; | ||
| 8 | const debug = std.debug; | ||
| 9 | const io = std.io; | ||
| 10 | |||
| 11 | const assert = debug.assert; | ||
| 12 | |||
| 13 | pub const Param = struct { | ||
| 14 | field: []const u8, | ||
| 15 | names: core.Names, | ||
| 16 | kind: Kind, | ||
| 17 | |||
| 18 | required: bool, | ||
| 19 | position: ?usize, | ||
| 20 | |||
| 21 | pub fn flag(field: []const u8, names: core.Names) Param { | ||
| 22 | return init( | ||
| 23 | field, | ||
| 24 | names, | ||
| 25 | Kind.Flag, | ||
| 26 | ); | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn option( | ||
| 30 | field: []const u8, | ||
| 31 | names: core.Names, | ||
| 32 | comptime parser: Parser, | ||
| 33 | ) Param { | ||
| 34 | return init( | ||
| 35 | field, | ||
| 36 | names, | ||
| 37 | Kind{ .Option = parser }, | ||
| 38 | ); | ||
| 39 | } | ||
| 40 | |||
| 41 | pub fn subcommand( | ||
| 42 | field: []const u8, | ||
| 43 | names: core.Names, | ||
| 44 | comptime command: Command, | ||
| 45 | ) Param { | ||
| 46 | return init( | ||
| 47 | field, | ||
| 48 | names, | ||
| 49 | Kind{ .Subcommand = command }, | ||
| 50 | ); | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn init(field: []const u8, names: core.Names, kind: Kind) Param { | ||
| 54 | return Param{ | ||
| 55 | .field = field, | ||
| 56 | .names = names, | ||
| 57 | .kind = kind, | ||
| 58 | .required = false, | ||
| 59 | .position = null, | ||
| 60 | }; | ||
| 61 | } | ||
| 62 | |||
| 63 | pub const Kind = union(enum) { | ||
| 64 | Flag, | ||
| 65 | Option: Parser, | ||
| 66 | Subcommand: Command, | ||
| 67 | }; | ||
| 68 | }; | ||
| 69 | |||
| 70 | const Opaque = @OpaqueType(); | ||
| 71 | pub const Command = struct { | ||
| 72 | params: []const Param, | ||
| 73 | |||
| 74 | Result: type, | ||
| 75 | default: *const Opaque, | ||
| 76 | |||
| 77 | pub fn init(comptime Result: type, default: *const Result, params: []const Param) Command { | ||
| 78 | return Command{ | ||
| 79 | .params = params, | ||
| 80 | .Result = Result, | ||
| 81 | .default = @ptrCast(*const Opaque, default), | ||
| 82 | }; | ||
| 83 | } | ||
| 84 | }; | ||
| 85 | |||
| 86 | pub const Parser = struct { | ||
| 87 | const UnsafeFunction = *const void; | ||
| 88 | |||
| 89 | FieldType: type, | ||
| 90 | Errors: type, | ||
| 91 | func: UnsafeFunction, | ||
| 92 | |||
| 93 | pub fn init(comptime FieldType: type, comptime Errors: type, func: ParseFunc(FieldType, Errors)) Parser { | ||
| 94 | return Parser{ | ||
| 95 | .FieldType = FieldType, | ||
| 96 | .Errors = Errors, | ||
| 97 | .func = @ptrCast(UnsafeFunction, func), | ||
| 98 | }; | ||
| 99 | } | ||
| 100 | |||
| 101 | fn parse(comptime parser: Parser, field_ptr: *parser.FieldType, arg: []const u8) parser.Errors!void { | ||
| 102 | return @ptrCast(ParseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg); | ||
| 103 | } | ||
| 104 | |||
| 105 | fn ParseFunc(comptime FieldType: type, comptime Errors: type) type { | ||
| 106 | return fn (*FieldType, []const u8) Errors!void; | ||
| 107 | } | ||
| 108 | |||
| 109 | pub fn int(comptime Int: type, comptime radix: u8) Parser { | ||
| 110 | const func = struct { | ||
| 111 | fn i(field_ptr: *Int, arg: []const u8) !void { | ||
| 112 | field_ptr.* = try fmt.parseInt(Int, arg, radix); | ||
| 113 | } | ||
| 114 | }.i; | ||
| 115 | return Parser.init(Int, @typeOf(func).ReturnType.ErrorSet, func); | ||
| 116 | } | ||
| 117 | |||
| 118 | const string = Parser.init([]const u8, error{}, struct { | ||
| 119 | fn s(field_ptr: *[]const u8, arg: []const u8) (error{}!void) { | ||
| 120 | field_ptr.* = arg; | ||
| 121 | } | ||
| 122 | }.s); | ||
| 123 | }; | ||
| 124 | |||
| 125 | pub fn Clap(comptime Result: type) type { | ||
| 126 | return struct { | ||
| 127 | const Self = this; | ||
| 128 | |||
| 129 | default: Result, | ||
| 130 | params: []const Param, | ||
| 131 | |||
| 132 | // TODO: pass-by-value | ||
| 133 | pub fn parse( | ||
| 134 | comptime clap: *const Self, | ||
| 135 | comptime Error: type, | ||
| 136 | iter: *core.ArgIterator(Error), | ||
| 137 | ) !Result { | ||
| 138 | // We initialize the core.Clap without any params, and fill them out in parseHelper. | ||
| 139 | var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter); | ||
| 140 | |||
| 141 | const top_level_command = comptime Command.init(Result, &clap.default, clap.params); | ||
| 142 | return try parseHelper(&top_level_command, Error, &c); | ||
| 143 | } | ||
| 144 | |||
| 145 | // TODO: pass-by-value | ||
| 146 | fn parseHelper( | ||
| 147 | comptime command: *const Command, | ||
| 148 | comptime Error: type, | ||
| 149 | clap: *core.Clap(usize, Error), | ||
| 150 | ) !command.Result { | ||
| 151 | var result = @ptrCast(*const command.Result, command.default).*; | ||
| 152 | |||
| 153 | var handled = comptime blk: { | ||
| 154 | var res: [command.params.len]bool = undefined; | ||
| 155 | for (command.params) |p, i| { | ||
| 156 | res[i] = !p.required; | ||
| 157 | } | ||
| 158 | |||
| 159 | break :blk res; | ||
| 160 | }; | ||
| 161 | |||
| 162 | // We replace the current clap with the commands parameters, so that we preserve the that | ||
| 163 | // claps state. This is important, as core.Clap could be in a Chaining state, and | ||
| 164 | // constructing a new core.Clap would skip the last chaining arguments. | ||
| 165 | clap.params = comptime blk: { | ||
| 166 | var res: [command.params.len]core.Param(usize) = undefined; | ||
| 167 | |||
| 168 | for (command.params) |p, i| { | ||
| 169 | const id = i; | ||
| 170 | res[id] = core.Param(usize){ | ||
| 171 | .id = id, | ||
| 172 | .takes_value = p.kind == Param.Kind.Option, | ||
| 173 | .names = p.names, | ||
| 174 | }; | ||
| 175 | } | ||
| 176 | |||
| 177 | break :blk res; | ||
| 178 | }; | ||
| 179 | |||
| 180 | var pos: usize = 0; | ||
| 181 | |||
| 182 | arg_loop: while (try clap.next()) |arg| : (pos += 1) { | ||
| 183 | inline for (command.params) |param, i| { | ||
| 184 | if (arg.param.id == i and (param.position orelse pos) == pos) { | ||
| 185 | handled[i] = true; | ||
| 186 | |||
| 187 | switch (param.kind) { | ||
| 188 | Param.Kind.Flag => { | ||
| 189 | getFieldPtr(&result, param.field).* = true; | ||
| 190 | }, | ||
| 191 | Param.Kind.Option => |parser| { | ||
| 192 | try parser.parse(getFieldPtr(&result, param.field), arg.value.?); | ||
| 193 | }, | ||
| 194 | Param.Kind.Subcommand => |sub_command| { | ||
| 195 | getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap); | ||
| 196 | |||
| 197 | // After parsing a subcommand, there should be no arguments left. | ||
| 198 | break :arg_loop; | ||
| 199 | }, | ||
| 200 | } | ||
| 201 | continue :arg_loop; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | return error.InvalidArgument; | ||
| 206 | } | ||
| 207 | |||
| 208 | for (handled) |h| { | ||
| 209 | if (!h) | ||
| 210 | return error.ParamNotHandled; | ||
| 211 | } | ||
| 212 | |||
| 213 | return result; | ||
| 214 | } | ||
| 215 | |||
| 216 | fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type { | ||
| 217 | var inst: Struct = undefined; | ||
| 218 | const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse { | ||
| 219 | return @typeOf(&@field(inst, field)); | ||
| 220 | }; | ||
| 221 | |||
| 222 | return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1 ..]); | ||
| 223 | } | ||
| 224 | |||
| 225 | fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) { | ||
| 226 | const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse { | ||
| 227 | return &@field(curr, field); | ||
| 228 | }; | ||
| 229 | |||
| 230 | return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1 ..]); | ||
| 231 | } | ||
| 232 | }; | ||
| 233 | } | ||
diff --git a/test.zig b/test.zig new file mode 100644 index 0000000..1708353 --- /dev/null +++ b/test.zig | |||
| @@ -0,0 +1,198 @@ | |||
| 1 | const std = @import("std"); | ||
| 2 | const clap = @import("clap.zig"); | ||
| 3 | |||
| 4 | const debug = std.debug; | ||
| 5 | const mem = std.mem; | ||
| 6 | |||
| 7 | const assert = debug.assert; | ||
| 8 | |||
| 9 | const ArgSliceIterator = clap.ArgSliceIterator; | ||
| 10 | const Names = clap.Names; | ||
| 11 | const Param = clap.Param(u8); | ||
| 12 | const Clap = clap.Clap(u8, ArgSliceIterator.Error); | ||
| 13 | const Arg = clap.Arg(u8); | ||
| 14 | |||
| 15 | fn testNoErr(params: []const Param, args: []const []const u8, results: []const Arg) void { | ||
| 16 | var arg_iter = ArgSliceIterator.init(args); | ||
| 17 | var c = Clap.init(params, &arg_iter.iter); | ||
| 18 | |||
| 19 | for (results) |res| { | ||
| 20 | const arg = (c.next() catch unreachable) orelse unreachable; | ||
| 21 | debug.assert(res.param == arg.param); | ||
| 22 | const expected_value = res.value orelse { | ||
| 23 | debug.assert(arg.value == null); | ||
| 24 | continue; | ||
| 25 | }; | ||
| 26 | const actual_value = arg.value orelse unreachable; | ||
| 27 | debug.assert(mem.eql(u8, expected_value, actual_value)); | ||
| 28 | } | ||
| 29 | |||
| 30 | if (c.next() catch unreachable) |_| { | ||
| 31 | unreachable; | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | test "clap: short" { | ||
| 36 | const params = []Param{ | ||
| 37 | Param.init(0, false, Names.short('a')), | ||
| 38 | Param.init(1, false, Names.short('b')), | ||
| 39 | Param.init(2, true, Names.short('c')), | ||
| 40 | }; | ||
| 41 | |||
| 42 | const a = ¶ms[0]; | ||
| 43 | const b = ¶ms[1]; | ||
| 44 | const c = ¶ms[2]; | ||
| 45 | |||
| 46 | testNoErr( | ||
| 47 | params, | ||
| 48 | [][]const u8{ | ||
| 49 | "-a", "-b", "-ab", "-ba", | ||
| 50 | "-c", "0", "-c=0", | ||
| 51 | "-ac", "0", "-ac=0", | ||
| 52 | }, | ||
| 53 | []const Arg{ | ||
| 54 | Arg.init(a, null), | ||
| 55 | Arg.init(b, null), | ||
| 56 | Arg.init(a, null), | ||
| 57 | Arg.init(b, null), | ||
| 58 | Arg.init(b, null), | ||
| 59 | Arg.init(a, null), | ||
| 60 | Arg.init(c, "0"), | ||
| 61 | Arg.init(c, "0"), | ||
| 62 | Arg.init(a, null), | ||
| 63 | Arg.init(c, "0"), | ||
| 64 | Arg.init(a, null), | ||
| 65 | Arg.init(c, "0"), | ||
| 66 | }, | ||
| 67 | ); | ||
| 68 | } | ||
| 69 | |||
| 70 | test "clap: long" { | ||
| 71 | const params = []Param{ | ||
| 72 | Param.init(0, false, Names.long("aa")), | ||
| 73 | Param.init(1, false, Names.long("bb")), | ||
| 74 | Param.init(2, true, Names.long("cc")), | ||
| 75 | }; | ||
| 76 | |||
| 77 | const aa = ¶ms[0]; | ||
| 78 | const bb = ¶ms[1]; | ||
| 79 | const cc = ¶ms[2]; | ||
| 80 | |||
| 81 | testNoErr( | ||
| 82 | params, | ||
| 83 | [][]const u8{ | ||
| 84 | "--aa", "--bb", | ||
| 85 | "--cc", "0", "--cc=0", | ||
| 86 | }, | ||
| 87 | []const Arg{ | ||
| 88 | Arg.init(aa, null), | ||
| 89 | Arg.init(bb, null), | ||
| 90 | Arg.init(cc, "0"), | ||
| 91 | Arg.init(cc, "0"), | ||
| 92 | }, | ||
| 93 | ); | ||
| 94 | } | ||
| 95 | |||
| 96 | test "clap: bare" { | ||
| 97 | const params = []Param{ | ||
| 98 | Param.init(0, false, Names.bare("aa")), | ||
| 99 | Param.init(1, false, Names.bare("bb")), | ||
| 100 | Param.init(2, true, Names.bare("cc")), | ||
| 101 | }; | ||
| 102 | |||
| 103 | const aa = ¶ms[0]; | ||
| 104 | const bb = ¶ms[1]; | ||
| 105 | const cc = ¶ms[2]; | ||
| 106 | |||
| 107 | testNoErr( | ||
| 108 | params, | ||
| 109 | [][]const u8{ | ||
| 110 | "aa", "bb", | ||
| 111 | "cc", "0", "cc=0", | ||
| 112 | }, | ||
| 113 | []const Arg{ | ||
| 114 | Arg.init(aa, null), | ||
| 115 | Arg.init(bb, null), | ||
| 116 | Arg.init(cc, "0"), | ||
| 117 | Arg.init(cc, "0"), | ||
| 118 | }, | ||
| 119 | ); | ||
| 120 | } | ||
| 121 | |||
| 122 | test "clap: none" { | ||
| 123 | const params = []Param{ | ||
| 124 | Param.init(0, true, Names.none()), | ||
| 125 | }; | ||
| 126 | |||
| 127 | testNoErr( | ||
| 128 | params, | ||
| 129 | [][]const u8{"aa", "bb"}, | ||
| 130 | []const Arg{ | ||
| 131 | Arg.init(¶ms[0], "aa"), | ||
| 132 | Arg.init(¶ms[0], "bb"), | ||
| 133 | }, | ||
| 134 | ); | ||
| 135 | } | ||
| 136 | |||
| 137 | test "clap: all" { | ||
| 138 | const params = []Param{ | ||
| 139 | Param.init(0, false, Names{ | ||
| 140 | .bare = "aa", | ||
| 141 | .short = 'a', | ||
| 142 | .long = "aa", | ||
| 143 | }), | ||
| 144 | Param.init(1, false, Names{ | ||
| 145 | .bare = "bb", | ||
| 146 | .short = 'b', | ||
| 147 | .long = "bb", | ||
| 148 | }), | ||
| 149 | Param.init(2, true, Names{ | ||
| 150 | .bare = "cc", | ||
| 151 | .short = 'c', | ||
| 152 | .long = "cc", | ||
| 153 | }), | ||
| 154 | Param.init(3, true, Names.none()), | ||
| 155 | }; | ||
| 156 | |||
| 157 | const aa = ¶ms[0]; | ||
| 158 | const bb = ¶ms[1]; | ||
| 159 | const cc = ¶ms[2]; | ||
| 160 | const bare = ¶ms[3]; | ||
| 161 | |||
| 162 | testNoErr( | ||
| 163 | params, | ||
| 164 | [][]const u8{ | ||
| 165 | "-a", "-b", "-ab", "-ba", | ||
| 166 | "-c", "0", "-c=0", | ||
| 167 | "-ac", "0", "-ac=0", | ||
| 168 | "--aa", "--bb", | ||
| 169 | "--cc", "0", "--cc=0", | ||
| 170 | "aa", "bb", | ||
| 171 | "cc", "0", "cc=0", | ||
| 172 | "something", | ||
| 173 | }, | ||
| 174 | []const Arg{ | ||
| 175 | Arg.init(aa, null), | ||
| 176 | Arg.init(bb, null), | ||
| 177 | Arg.init(aa, null), | ||
| 178 | Arg.init(bb, null), | ||
| 179 | Arg.init(bb, null), | ||
| 180 | Arg.init(aa, null), | ||
| 181 | Arg.init(cc, "0"), | ||
| 182 | Arg.init(cc, "0"), | ||
| 183 | Arg.init(aa, null), | ||
| 184 | Arg.init(cc, "0"), | ||
| 185 | Arg.init(aa, null), | ||
| 186 | Arg.init(cc, "0"), | ||
| 187 | Arg.init(aa, null), | ||
| 188 | Arg.init(bb, null), | ||
| 189 | Arg.init(cc, "0"), | ||
| 190 | Arg.init(cc, "0"), | ||
| 191 | Arg.init(aa, null), | ||
| 192 | Arg.init(bb, null), | ||
| 193 | Arg.init(cc, "0"), | ||
| 194 | Arg.init(cc, "0"), | ||
| 195 | Arg.init(bare, "something"), | ||
| 196 | }, | ||
| 197 | ); | ||
| 198 | } | ||
diff --git a/tests/core.zig b/tests/core.zig deleted file mode 100644 index 765b161..0000000 --- a/tests/core.zig +++ /dev/null | |||
| @@ -1,120 +0,0 @@ | |||
| 1 | const std = @import("std"); | ||
| 2 | const clap = @import("../index.zig"); | ||
| 3 | |||
| 4 | const debug = std.debug; | ||
| 5 | const mem = std.mem; | ||
| 6 | const core = clap.core; | ||
| 7 | |||
| 8 | const assert = debug.assert; | ||
| 9 | |||
| 10 | const ArgSliceIterator = core.ArgSliceIterator; | ||
| 11 | const Names = core.Names; | ||
| 12 | const Param = core.Param; | ||
| 13 | const Clap = core.Clap; | ||
| 14 | |||
| 15 | fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void { | ||
| 16 | var arg_iter = ArgSliceIterator.init(args); | ||
| 17 | var iter = Clap(u8, ArgSliceIterator.Error).init(params, &arg_iter.iter); | ||
| 18 | |||
| 19 | var i: usize = 0; | ||
| 20 | while (iter.next() catch unreachable) |arg| : (i += 1) { | ||
| 21 | debug.assert(ids[i] == arg.param.id); | ||
| 22 | const expected_value = values[i] orelse { | ||
| 23 | debug.assert(arg.value == null); | ||
| 24 | continue; | ||
| 25 | }; | ||
| 26 | const actual_value = arg.value orelse unreachable; | ||
| 27 | |||
| 28 | debug.assert(mem.eql(u8, expected_value, actual_value)); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | test "clap.core: short" { | ||
| 33 | const params = []Param(u8){ | ||
| 34 | Param(u8).init(0, false, Names.short('a')), | ||
| 35 | Param(u8).init(1, false, Names.short('b')), | ||
| 36 | Param(u8).init(2, true, Names.short('c')), | ||
| 37 | }; | ||
| 38 | |||
| 39 | testNoErr(params, [][]const u8{"-a"}, []u8{0}, []?[]const u8{null}); | ||
| 40 | testNoErr(params, [][]const u8{ "-a", "-b" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 41 | testNoErr(params, [][]const u8{"-ab"}, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 42 | testNoErr(params, [][]const u8{"-c=100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 43 | testNoErr(params, [][]const u8{"-c100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 44 | testNoErr(params, [][]const u8{ "-c", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 45 | testNoErr(params, [][]const u8{ "-abc", "100" }, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); | ||
| 46 | testNoErr(params, [][]const u8{"-abc=100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); | ||
| 47 | testNoErr(params, [][]const u8{"-abc100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); | ||
| 48 | } | ||
| 49 | |||
| 50 | test "clap.core: long" { | ||
| 51 | const params = []Param(u8){ | ||
| 52 | Param(u8).init(0, false, Names.long("aa")), | ||
| 53 | Param(u8).init(1, false, Names.long("bb")), | ||
| 54 | Param(u8).init(2, true, Names.long("cc")), | ||
| 55 | }; | ||
| 56 | |||
| 57 | testNoErr(params, [][]const u8{"--aa"}, []u8{0}, []?[]const u8{null}); | ||
| 58 | testNoErr(params, [][]const u8{ "--aa", "--bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 59 | testNoErr(params, [][]const u8{"--cc=100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 60 | testNoErr(params, [][]const u8{ "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 61 | } | ||
| 62 | |||
| 63 | test "clap.core: bare" { | ||
| 64 | const params = []Param(u8){ | ||
| 65 | Param(u8).init(0, false, Names.bare("aa")), | ||
| 66 | Param(u8).init(1, false, Names.bare("bb")), | ||
| 67 | Param(u8).init(2, true, Names.bare("cc")), | ||
| 68 | }; | ||
| 69 | |||
| 70 | testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{null}); | ||
| 71 | testNoErr(params, [][]const u8{ "aa", "bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 72 | testNoErr(params, [][]const u8{"cc=100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 73 | testNoErr(params, [][]const u8{ "cc", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 74 | } | ||
| 75 | |||
| 76 | test "clap.core: none" { | ||
| 77 | const params = []Param(u8){Param(u8).init(0, true, Names.none())}; | ||
| 78 | |||
| 79 | testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{"aa"}); | ||
| 80 | } | ||
| 81 | |||
| 82 | test "clap.core: all" { | ||
| 83 | const params = []Param(u8){ | ||
| 84 | Param(u8).init(0, false, Names{ | ||
| 85 | .bare = "aa", | ||
| 86 | .short = 'a', | ||
| 87 | .long = "aa", | ||
| 88 | }), | ||
| 89 | Param(u8).init(1, false, Names{ | ||
| 90 | .bare = "bb", | ||
| 91 | .short = 'b', | ||
| 92 | .long = "bb", | ||
| 93 | }), | ||
| 94 | Param(u8).init(2, true, Names{ | ||
| 95 | .bare = "cc", | ||
| 96 | .short = 'c', | ||
| 97 | .long = "cc", | ||
| 98 | }), | ||
| 99 | Param(u8).init(3, true, Names.none()), | ||
| 100 | }; | ||
| 101 | |||
| 102 | testNoErr(params, [][]const u8{"-a"}, []u8{0}, []?[]const u8{null}); | ||
| 103 | testNoErr(params, [][]const u8{ "-a", "-b" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 104 | testNoErr(params, [][]const u8{"-ab"}, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 105 | testNoErr(params, [][]const u8{"-c=100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 106 | testNoErr(params, [][]const u8{"-c100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 107 | testNoErr(params, [][]const u8{ "-c", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 108 | testNoErr(params, [][]const u8{ "-abc", "100" }, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); | ||
| 109 | testNoErr(params, [][]const u8{"-abc=100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); | ||
| 110 | testNoErr(params, [][]const u8{"-abc100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); | ||
| 111 | testNoErr(params, [][]const u8{"--aa"}, []u8{0}, []?[]const u8{null}); | ||
| 112 | testNoErr(params, [][]const u8{ "--aa", "--bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 113 | testNoErr(params, [][]const u8{"--cc=100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 114 | testNoErr(params, [][]const u8{ "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 115 | testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{null}); | ||
| 116 | testNoErr(params, [][]const u8{ "aa", "bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); | ||
| 117 | testNoErr(params, [][]const u8{"cc=100"}, []u8{2}, []?[]const u8{"100"}); | ||
| 118 | testNoErr(params, [][]const u8{ "cc", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 119 | testNoErr(params, [][]const u8{"dd"}, []u8{3}, []?[]const u8{"dd"}); | ||
| 120 | } | ||
diff --git a/tests/extended.zig b/tests/extended.zig deleted file mode 100644 index 9670814..0000000 --- a/tests/extended.zig +++ /dev/null | |||
| @@ -1,306 +0,0 @@ | |||
| 1 | const std = @import("std"); | ||
| 2 | const clap = @import("../index.zig"); | ||
| 3 | |||
| 4 | const debug = std.debug; | ||
| 5 | const mem = std.mem; | ||
| 6 | const core = clap.core; | ||
| 7 | const extended = clap.extended; | ||
| 8 | |||
| 9 | const assert = debug.assert; | ||
| 10 | |||
| 11 | const ArgSliceIterator = core.ArgSliceIterator; | ||
| 12 | const Names = core.Names; | ||
| 13 | const Clap = extended.Clap; | ||
| 14 | const Param = extended.Param; | ||
| 15 | const Parser = extended.Parser; | ||
| 16 | |||
| 17 | pub fn Test(comptime Expect: type) type { | ||
| 18 | return struct { | ||
| 19 | const Self = this; | ||
| 20 | |||
| 21 | args: []const []const u8, | ||
| 22 | kind: Kind, | ||
| 23 | |||
| 24 | const Kind = union(enum) { | ||
| 25 | Success: Expect, | ||
| 26 | Fail: error, | ||
| 27 | }; | ||
| 28 | |||
| 29 | pub fn success(args: []const []const u8, expected: Expect) Self { | ||
| 30 | return Self{ | ||
| 31 | .args = args, | ||
| 32 | .kind = Kind{ .Success = expected }, | ||
| 33 | }; | ||
| 34 | } | ||
| 35 | |||
| 36 | pub fn fail(args: []const []const u8, err: error) Self { | ||
| 37 | return Self{ | ||
| 38 | .args = args, | ||
| 39 | .kind = Kind{ .Fail = err }, | ||
| 40 | }; | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn run(t: Self, comptime parser: var) void { | ||
| 44 | var iter = ArgSliceIterator.init(t.args); | ||
| 45 | const actual = parser.parse(ArgSliceIterator.Error, &iter.iter); | ||
| 46 | |||
| 47 | switch (t.kind) { | ||
| 48 | Kind.Success => |expected| { | ||
| 49 | const actual_value = actual catch unreachable; | ||
| 50 | inline for (@typeInfo(Expect).Struct.fields) |field| { | ||
| 51 | assert(@field(expected, field.name) == @field(actual_value, field.name)); | ||
| 52 | } | ||
| 53 | }, | ||
| 54 | Kind.Fail => |expected| { | ||
| 55 | if (actual) |_| { | ||
| 56 | unreachable; | ||
| 57 | } else |actual_err| { | ||
| 58 | assert(actual_err == expected); | ||
| 59 | } | ||
| 60 | }, | ||
| 61 | } | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | } | ||
| 65 | |||
| 66 | test "clap.extended: short" { | ||
| 67 | const S = struct { | ||
| 68 | a: bool, | ||
| 69 | b: u8, | ||
| 70 | }; | ||
| 71 | |||
| 72 | const parser = comptime Clap(S){ | ||
| 73 | .default = S{ | ||
| 74 | .a = false, | ||
| 75 | .b = 0, | ||
| 76 | }, | ||
| 77 | .params = []Param{ | ||
| 78 | p: { | ||
| 79 | var res = Param.flag("a", Names.short('a')); | ||
| 80 | res.required = true; | ||
| 81 | res.position = 0; | ||
| 82 | break :p res; | ||
| 83 | }, | ||
| 84 | Param.option("b", Names.short('b'), Parser.int(u8, 10)), | ||
| 85 | }, | ||
| 86 | }; | ||
| 87 | |||
| 88 | const T = Test(S); | ||
| 89 | const tests = []T{ | ||
| 90 | T.success( | ||
| 91 | [][]const u8{"-a"}, | ||
| 92 | S{ | ||
| 93 | .a = true, | ||
| 94 | .b = 0, | ||
| 95 | }, | ||
| 96 | ), | ||
| 97 | T.success( | ||
| 98 | [][]const u8{ "-a", "-b", "100" }, | ||
| 99 | S{ | ||
| 100 | .a = true, | ||
| 101 | .b = 100, | ||
| 102 | }, | ||
| 103 | ), | ||
| 104 | T.success( | ||
| 105 | [][]const u8{ "-a", "-b=100" }, | ||
| 106 | S{ | ||
| 107 | .a = true, | ||
| 108 | .b = 100, | ||
| 109 | }, | ||
| 110 | ), | ||
| 111 | T.success( | ||
| 112 | [][]const u8{ "-a", "-b100" }, | ||
| 113 | S{ | ||
| 114 | .a = true, | ||
| 115 | .b = 100, | ||
| 116 | }, | ||
| 117 | ), | ||
| 118 | T.success( | ||
| 119 | [][]const u8{ "-ab", "100" }, | ||
| 120 | S{ | ||
| 121 | .a = true, | ||
| 122 | .b = 100, | ||
| 123 | }, | ||
| 124 | ), | ||
| 125 | T.success( | ||
| 126 | [][]const u8{"-ab=100"}, | ||
| 127 | S{ | ||
| 128 | .a = true, | ||
| 129 | .b = 100, | ||
| 130 | }, | ||
| 131 | ), | ||
| 132 | T.success( | ||
| 133 | [][]const u8{"-ab100"}, | ||
| 134 | S{ | ||
| 135 | .a = true, | ||
| 136 | .b = 100, | ||
| 137 | }, | ||
| 138 | ), | ||
| 139 | T.fail( | ||
| 140 | [][]const u8{"-q"}, | ||
| 141 | error.InvalidArgument, | ||
| 142 | ), | ||
| 143 | T.fail( | ||
| 144 | [][]const u8{"--a"}, | ||
| 145 | error.InvalidArgument, | ||
| 146 | ), | ||
| 147 | T.fail( | ||
| 148 | [][]const u8{"-b=100"}, | ||
| 149 | error.ParamNotHandled, | ||
| 150 | ), | ||
| 151 | T.fail( | ||
| 152 | [][]const u8{ "-b=100", "-a" }, | ||
| 153 | error.InvalidArgument, | ||
| 154 | ), | ||
| 155 | }; | ||
| 156 | |||
| 157 | for (tests) |t| { | ||
| 158 | t.run(parser); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | test "clap.extended: long" { | ||
| 163 | const S = struct { | ||
| 164 | a: bool, | ||
| 165 | b: u8, | ||
| 166 | }; | ||
| 167 | |||
| 168 | const parser = comptime Clap(S){ | ||
| 169 | .default = S{ | ||
| 170 | .a = false, | ||
| 171 | .b = 0, | ||
| 172 | }, | ||
| 173 | .params = []Param{ | ||
| 174 | p: { | ||
| 175 | var res = Param.flag("a", Names.long("a")); | ||
| 176 | res.required = true; | ||
| 177 | res.position = 0; | ||
| 178 | break :p res; | ||
| 179 | }, | ||
| 180 | Param.option("b", Names.long("b"), Parser.int(u8, 10)), | ||
| 181 | }, | ||
| 182 | }; | ||
| 183 | |||
| 184 | const T = Test(S); | ||
| 185 | const tests = []T{ | ||
| 186 | T.success( | ||
| 187 | [][]const u8{"--a"}, | ||
| 188 | S{ | ||
| 189 | .a = true, | ||
| 190 | .b = 0, | ||
| 191 | }, | ||
| 192 | ), | ||
| 193 | T.success( | ||
| 194 | [][]const u8{ "--a", "--b", "100" }, | ||
| 195 | S{ | ||
| 196 | .a = true, | ||
| 197 | .b = 100, | ||
| 198 | }, | ||
| 199 | ), | ||
| 200 | T.success( | ||
| 201 | [][]const u8{ "--a", "--b=100" }, | ||
| 202 | S{ | ||
| 203 | .a = true, | ||
| 204 | .b = 100, | ||
| 205 | }, | ||
| 206 | ), | ||
| 207 | T.fail( | ||
| 208 | [][]const u8{"--a=100"}, | ||
| 209 | error.DoesntTakeValue, | ||
| 210 | ), | ||
| 211 | T.fail( | ||
| 212 | [][]const u8{"--q"}, | ||
| 213 | error.InvalidArgument, | ||
| 214 | ), | ||
| 215 | T.fail( | ||
| 216 | [][]const u8{"-a"}, | ||
| 217 | error.InvalidArgument, | ||
| 218 | ), | ||
| 219 | T.fail( | ||
| 220 | [][]const u8{"--b=100"}, | ||
| 221 | error.ParamNotHandled, | ||
| 222 | ), | ||
| 223 | T.fail( | ||
| 224 | [][]const u8{ "--b=100", "--a" }, | ||
| 225 | error.InvalidArgument, | ||
| 226 | ), | ||
| 227 | }; | ||
| 228 | |||
| 229 | for (tests) |t| { | ||
| 230 | t.run(parser); | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | test "clap.extended: bare" { | ||
| 235 | const S = struct { | ||
| 236 | a: bool, | ||
| 237 | b: u8, | ||
| 238 | }; | ||
| 239 | |||
| 240 | const parser = comptime Clap(S){ | ||
| 241 | .default = S{ | ||
| 242 | .a = false, | ||
| 243 | .b = 0, | ||
| 244 | }, | ||
| 245 | .params = []Param{ | ||
| 246 | p: { | ||
| 247 | var res = Param.flag("a", Names.bare("a")); | ||
| 248 | res.required = true; | ||
| 249 | res.position = 0; | ||
| 250 | break :p res; | ||
| 251 | }, | ||
| 252 | Param.option("b", Names.bare("b"), Parser.int(u8, 10)), | ||
| 253 | }, | ||
| 254 | }; | ||
| 255 | |||
| 256 | const T = Test(S); | ||
| 257 | const tests = []T{ | ||
| 258 | T.success( | ||
| 259 | [][]const u8{"a"}, | ||
| 260 | S{ | ||
| 261 | .a = true, | ||
| 262 | .b = 0, | ||
| 263 | }, | ||
| 264 | ), | ||
| 265 | T.success( | ||
| 266 | [][]const u8{ "a", "b", "100" }, | ||
| 267 | S{ | ||
| 268 | .a = true, | ||
| 269 | .b = 100, | ||
| 270 | }, | ||
| 271 | ), | ||
| 272 | T.success( | ||
| 273 | [][]const u8{ "a", "b=100" }, | ||
| 274 | S{ | ||
| 275 | .a = true, | ||
| 276 | .b = 100, | ||
| 277 | }, | ||
| 278 | ), | ||
| 279 | T.fail( | ||
| 280 | [][]const u8{"a=100"}, | ||
| 281 | error.DoesntTakeValue, | ||
| 282 | ), | ||
| 283 | T.fail( | ||
| 284 | [][]const u8{"--a"}, | ||
| 285 | error.InvalidArgument, | ||
| 286 | ), | ||
| 287 | T.fail( | ||
| 288 | [][]const u8{"-a"}, | ||
| 289 | error.InvalidArgument, | ||
| 290 | ), | ||
| 291 | T.fail( | ||
| 292 | [][]const u8{"b=100"}, | ||
| 293 | error.ParamNotHandled, | ||
| 294 | ), | ||
| 295 | T.fail( | ||
| 296 | [][]const u8{ "b=100", "--a" }, | ||
| 297 | error.InvalidArgument, | ||
| 298 | ), | ||
| 299 | }; | ||
| 300 | |||
| 301 | for (tests) |t| { | ||
| 302 | t.run(parser); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | // TODO: Test sub commands and sub field access | ||