diff options
| author | 2018-05-19 17:03:17 +0200 | |
|---|---|---|
| committer | 2018-05-19 17:03:17 +0200 | |
| commit | 6193b5eb334d79238f3796f4fac2821051c24814 (patch) | |
| tree | d4194aa220a36ab31c66cb6ac7184033f68b341a | |
| parent | Finished the first draft of the core (diff) | |
| download | zig-clap-6193b5eb334d79238f3796f4fac2821051c24814.tar.gz zig-clap-6193b5eb334d79238f3796f4fac2821051c24814.tar.xz zig-clap-6193b5eb334d79238f3796f4fac2821051c24814.zip | |
core.zig tested and ready for action!
Diffstat (limited to '')
| -rw-r--r-- | core.zig | 217 |
1 files changed, 185 insertions, 32 deletions
| @@ -3,7 +3,8 @@ const builtin = @import("builtin"); | |||
| 3 | 3 | ||
| 4 | const os = std.os; | 4 | const os = std.os; |
| 5 | const heap = std.heap; | 5 | const heap = std.heap; |
| 6 | const is_windows = builtin.os == Os.windows; | 6 | const mem = std.mem; |
| 7 | const debug = std.debug; | ||
| 7 | 8 | ||
| 8 | /// Represents a parameter for the command line. | 9 | /// Represents a parameter for the command line. |
| 9 | /// Parameters come in three kinds: | 10 | /// Parameters come in three kinds: |
| @@ -22,8 +23,8 @@ const is_windows = builtin.os == Os.windows; | |||
| 22 | /// * They can take a value two different ways. | 23 | /// * They can take a value two different ways. |
| 23 | /// * "--long-arg value" | 24 | /// * "--long-arg value" |
| 24 | /// * "--long-arg=value" | 25 | /// * "--long-arg=value" |
| 25 | /// * Value ("some-value"): Should be used as the primary of the program, like a filename or an | 26 | /// * Value ("some-value"): Should be used as the primary parameter of the program, like a |
| 26 | /// expression to parse. | 27 | /// filename or an expression to parse. |
| 27 | pub fn Param(comptime Id: type) type { | 28 | pub fn Param(comptime Id: type) type { |
| 28 | return struct { | 29 | return struct { |
| 29 | const Self = this; | 30 | const Self = this; |
| @@ -37,12 +38,21 @@ pub fn Param(comptime Id: type) type { | |||
| 37 | /// If ::name.len == 0, then it's a value parameter: "some-command value". | 38 | /// If ::name.len == 0, then it's a value parameter: "some-command value". |
| 38 | /// If ::name.len == 1, then it's a short parameter: "some-command -s". | 39 | /// If ::name.len == 1, then it's a short parameter: "some-command -s". |
| 39 | /// If ::name.len > 1, then it's a long parameter: "some-command --long". | 40 | /// If ::name.len > 1, then it's a long parameter: "some-command --long". |
| 40 | pub fn init(id: Id, name: []const u8) Self { | 41 | pub fn init(id: Id, name: []const u8, takes_value: bool) Self { |
| 41 | return { | 42 | return Self{ |
| 42 | .id = id, | 43 | .id = id, |
| 43 | .short = if (name.len == 1) name[0] else null, | 44 | .short = if (name.len == 1) name[0] else null, |
| 44 | .long = if (name.len > 1) name else null, | 45 | .long = if (name.len > 1) name else null, |
| 45 | .takes_value = false, | 46 | .takes_value = takes_value, |
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 50 | pub fn both(id: Id, short: u8, long: []const u8, takes_value: bool) Self { | ||
| 51 | return Self{ | ||
| 52 | .id = id, | ||
| 53 | .short = short, | ||
| 54 | .long = long, | ||
| 55 | .takes_value = takes_value, | ||
| 46 | }; | 56 | }; |
| 47 | } | 57 | } |
| 48 | 58 | ||
| @@ -73,6 +83,64 @@ pub fn Arg(comptime Id: type) type { | |||
| 73 | }; | 83 | }; |
| 74 | } | 84 | } |
| 75 | 85 | ||
| 86 | pub const ArgIterator = struct { | ||
| 87 | const Error = error{OutOfMemory}; | ||
| 88 | |||
| 89 | nextFn: fn(iter: &ArgIterator, allocator: &mem.Allocator) Error!?[]const u8, | ||
| 90 | |||
| 91 | pub fn next(iter: &ArgIterator, allocator: &mem.Allocator) Error!?[]const u8 { | ||
| 92 | return iter.nextFn(iter, allocator); | ||
| 93 | } | ||
| 94 | }; | ||
| 95 | |||
| 96 | pub const ArgSliceIterator = struct { | ||
| 97 | args: []const []const u8, | ||
| 98 | index: usize, | ||
| 99 | iter: ArgIterator, | ||
| 100 | |||
| 101 | pub fn init(args: []const []const u8) ArgSliceIterator { | ||
| 102 | return ArgSliceIterator { | ||
| 103 | .args = args, | ||
| 104 | .index = 0, | ||
| 105 | .iter = ArgIterator { | ||
| 106 | .nextFn = nextFn, | ||
| 107 | }, | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 { | ||
| 112 | const self = @fieldParentPtr(ArgSliceIterator, "iter", iter); | ||
| 113 | if (self.args.len <= self.index) | ||
| 114 | return null; | ||
| 115 | |||
| 116 | defer self.index += 1; | ||
| 117 | return self.args[self.index]; | ||
| 118 | } | ||
| 119 | }; | ||
| 120 | |||
| 121 | pub const OsArgIterator = struct { | ||
| 122 | args: os.ArgIterator, | ||
| 123 | iter: ArgIterator, | ||
| 124 | |||
| 125 | pub fn init() OsArgIterator { | ||
| 126 | return OsArgIterator { | ||
| 127 | .args = os.args(), | ||
| 128 | .iter = ArgIterator { | ||
| 129 | .nextFn = nextFn, | ||
| 130 | }, | ||
| 131 | }; | ||
| 132 | } | ||
| 133 | |||
| 134 | fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error![]const u8 { | ||
| 135 | const self = @fieldParentPtr(OsArgIterator, "iter", iter); | ||
| 136 | if (builtin.os == builtin.Os.Windows) { | ||
| 137 | return try self.args.next(allocator); | ||
| 138 | } else { | ||
| 139 | return self.args.nextPoxix(); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | }; | ||
| 143 | |||
| 76 | /// A ::CustomIterator with a default Windows buffer size. | 144 | /// A ::CustomIterator with a default Windows buffer size. |
| 77 | pub fn Iterator(comptime Id: type) type { | 145 | pub fn Iterator(comptime Id: type) type { |
| 78 | return struct { | 146 | return struct { |
| @@ -85,24 +153,25 @@ pub fn Iterator(comptime Id: type) type { | |||
| 85 | const Chaining = struct { | 153 | const Chaining = struct { |
| 86 | arg: []const u8, | 154 | arg: []const u8, |
| 87 | index: usize, | 155 | index: usize, |
| 88 | next: &const Param, | 156 | param: &const Param(Id), |
| 89 | }; | 157 | }; |
| 90 | }; | 158 | }; |
| 91 | 159 | ||
| 92 | arena: &heap.ArenaAllocator, | 160 | arena: heap.ArenaAllocator, |
| 93 | params: Param(Id), | 161 | params: []const Param(Id), |
| 94 | args: os.ArgIterator, | 162 | inner: &ArgIterator, |
| 95 | state: State, | 163 | state: State, |
| 96 | command: []const u8, | 164 | command: []const u8, |
| 97 | 165 | ||
| 98 | pub fn init(params: []const Param(Id), allocator: &mem.Allocator) !Self { | 166 | pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) !Self { |
| 99 | var res = Self { | 167 | var res = Self { |
| 100 | .allocator = heap.ArenaAllocator.init(allocator), | 168 | .arena = heap.ArenaAllocator.init(allocator), |
| 101 | .params = params, | 169 | .params = params, |
| 102 | .args = os.args(), | 170 | .inner = inner, |
| 171 | .state = State.Normal, | ||
| 103 | .command = undefined, | 172 | .command = undefined, |
| 104 | }; | 173 | }; |
| 105 | res.command = try res.innerNext(); | 174 | res.command = (try res.innerNext()) ?? unreachable; |
| 106 | 175 | ||
| 107 | return res; | 176 | return res; |
| 108 | } | 177 | } |
| @@ -129,10 +198,10 @@ pub fn Iterator(comptime Id: type) type { | |||
| 129 | 198 | ||
| 130 | if (mem.startsWith(u8, arg, "--")) { | 199 | if (mem.startsWith(u8, arg, "--")) { |
| 131 | arg = arg[2..]; | 200 | arg = arg[2..]; |
| 132 | kind = Arg.Kind.Long; | 201 | kind = ArgInfo.Kind.Long; |
| 133 | } else if (mem.startsWith(u8, arg, "-")) { | 202 | } else if (mem.startsWith(u8, arg, "-")) { |
| 134 | arg = arg[1..]; | 203 | arg = arg[1..]; |
| 135 | kind = Arg.Kind.Short; | 204 | kind = ArgInfo.Kind.Short; |
| 136 | } | 205 | } |
| 137 | 206 | ||
| 138 | if (arg.len == 0) | 207 | if (arg.len == 0) |
| @@ -143,20 +212,34 @@ pub fn Iterator(comptime Id: type) type { | |||
| 143 | 212 | ||
| 144 | const arg = arg_info.arg; | 213 | const arg = arg_info.arg; |
| 145 | const kind = arg_info.kind; | 214 | const kind = arg_info.kind; |
| 215 | const eql_index = mem.indexOfScalar(u8, arg, '='); | ||
| 146 | 216 | ||
| 147 | for (iter.params) |*param| { | 217 | for (iter.params) |*param| { |
| 148 | switch (kind) { | 218 | switch (kind) { |
| 149 | Arg.Kind.Long => { | 219 | ArgInfo.Kind.Long => { |
| 150 | const long = param.long ?? continue; | 220 | const long = param.long ?? continue; |
| 151 | if (!mem.eql(u8, arg, long)) | 221 | const name = if (eql_index) |i| arg[0..i] else arg; |
| 222 | const maybe_value = if (eql_index) |i| arg[i + 1..] else null; | ||
| 223 | |||
| 224 | if (!mem.eql(u8, name, long)) | ||
| 152 | continue; | 225 | continue; |
| 153 | if (!param.takes_value) | 226 | if (!param.takes_value) { |
| 227 | if (maybe_value != null) | ||
| 228 | return error.DoesntTakeValue; | ||
| 229 | |||
| 154 | return Arg(Id).init(param.id, null); | 230 | return Arg(Id).init(param.id, null); |
| 231 | } | ||
| 232 | |||
| 233 | const value = blk: { | ||
| 234 | if (maybe_value) |v| | ||
| 235 | break :blk v; | ||
| 236 | |||
| 237 | break :blk (try iter.innerNext()) ?? return error.MissingValue; | ||
| 238 | }; | ||
| 155 | 239 | ||
| 156 | const value = (try iter.innerNext()) ?? return error.MissingValue; | ||
| 157 | return Arg(Id).init(param.id, value); | 240 | return Arg(Id).init(param.id, value); |
| 158 | }, | 241 | }, |
| 159 | Arg.Kind.Short => { | 242 | ArgInfo.Kind.Short => { |
| 160 | const short = param.short ?? continue; | 243 | const short = param.short ?? continue; |
| 161 | if (short != arg[0]) | 244 | if (short != arg[0]) |
| 162 | continue; | 245 | continue; |
| @@ -164,10 +247,10 @@ pub fn Iterator(comptime Id: type) type { | |||
| 164 | return try iter.chainging(State.Chaining { | 247 | return try iter.chainging(State.Chaining { |
| 165 | .arg = full_arg, | 248 | .arg = full_arg, |
| 166 | .index = (full_arg.len - arg.len) + 1, | 249 | .index = (full_arg.len - arg.len) + 1, |
| 167 | .next = param, | 250 | .param = param, |
| 168 | }); | 251 | }); |
| 169 | }, | 252 | }, |
| 170 | Arg.Kind.Value => { | 253 | ArgInfo.Kind.Value => { |
| 171 | if (param.long) |_| continue; | 254 | if (param.long) |_| continue; |
| 172 | if (param.short) |_| continue; | 255 | if (param.short) |_| continue; |
| 173 | 256 | ||
| @@ -175,17 +258,21 @@ pub fn Iterator(comptime Id: type) type { | |||
| 175 | } | 258 | } |
| 176 | } | 259 | } |
| 177 | } | 260 | } |
| 261 | |||
| 262 | return error.InvalidArgument; | ||
| 178 | }, | 263 | }, |
| 179 | State.Chaining => |state| return try iter.chainging(state), | 264 | @TagType(State).Chaining => |state| return try iter.chainging(state), |
| 180 | } | 265 | } |
| 181 | } | 266 | } |
| 182 | 267 | ||
| 183 | fn chainging(iter: &const Self, state: &const State.Chaining) !?Arg(Id) { | 268 | fn chainging(iter: &Self, state: &const State.Chaining) !?Arg(Id) { |
| 184 | const arg = state.arg; | 269 | const arg = state.arg; |
| 185 | const index = state.index; | 270 | const index = state.index; |
| 186 | const curr_param = state.param; | 271 | const curr_param = state.param; |
| 187 | 272 | ||
| 188 | if (curr_param.takes_value) { | 273 | if (curr_param.takes_value) { |
| 274 | iter.state = State.Normal; | ||
| 275 | |||
| 189 | if (arg.len <= index) { | 276 | if (arg.len <= index) { |
| 190 | const value = (try iter.innerNext()) ?? return error.MissingValue; | 277 | const value = (try iter.innerNext()) ?? return error.MissingValue; |
| 191 | return Arg(Id).init(curr_param.id, value); | 278 | return Arg(Id).init(curr_param.id, value); |
| @@ -208,7 +295,7 @@ pub fn Iterator(comptime Id: type) type { | |||
| 208 | if (short != arg[index]) | 295 | if (short != arg[index]) |
| 209 | continue; | 296 | continue; |
| 210 | 297 | ||
| 211 | iter.State = State { .Chaining = State.Chaining { | 298 | iter.state = State { .Chaining = State.Chaining { |
| 212 | .arg = arg, | 299 | .arg = arg, |
| 213 | .index = index + 1, | 300 | .index = index + 1, |
| 214 | .param = param, | 301 | .param = param, |
| @@ -220,12 +307,78 @@ pub fn Iterator(comptime Id: type) type { | |||
| 220 | return error.InvalidArgument; | 307 | return error.InvalidArgument; |
| 221 | } | 308 | } |
| 222 | 309 | ||
| 223 | fn innerNext(iter: &Self) os.ArgIterator.NextError!?[]const u8 { | 310 | fn innerNext(iter: &Self) !?[]const u8 { |
| 224 | if (builtin.os == Os.windows) { | 311 | return try iter.inner.next(&iter.arena.allocator); |
| 225 | return try iter.args.next(&iter.arena.allocator); | ||
| 226 | } else { | ||
| 227 | return iter.args.nextPosix(); | ||
| 228 | } | ||
| 229 | } | 312 | } |
| 313 | }; | ||
| 314 | } | ||
| 315 | |||
| 316 | fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void { | ||
| 317 | var arg_iter = ArgSliceIterator.init(args); | ||
| 318 | var iter = Iterator(u8).init(params, &arg_iter.iter, debug.global_allocator) catch unreachable; | ||
| 319 | |||
| 320 | var i: usize = 0; | ||
| 321 | while (iter.next() catch unreachable) |arg| : (i += 1) { | ||
| 322 | debug.assert(ids[i] == arg.id); | ||
| 323 | const expected_value = values[i] ?? { | ||
| 324 | debug.assert(arg.value == null); | ||
| 325 | continue; | ||
| 326 | }; | ||
| 327 | const actual_value = arg.value ?? unreachable; | ||
| 328 | |||
| 329 | debug.assert(mem.eql(u8, expected_value, actual_value)); | ||
| 230 | } | 330 | } |
| 231 | } | 331 | } |
| 332 | |||
| 333 | test "clap.parse: short" { | ||
| 334 | const params = []Param(u8) { | ||
| 335 | Param(u8).init(0, "a", false), | ||
| 336 | Param(u8).init(1, "b", false), | ||
| 337 | Param(u8).init(2, "c", true), | ||
| 338 | }; | ||
| 339 | |||
| 340 | testNoErr(params, [][]const u8 { "command", "-a" }, []u8{0}, []?[]const u8{null}); | ||
| 341 | testNoErr(params, [][]const u8 { "command", "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null}); | ||
| 342 | testNoErr(params, [][]const u8 { "command", "-ab" }, []u8{0,1}, []?[]const u8{null,null}); | ||
| 343 | testNoErr(params, [][]const u8 { "command", "-c=100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 344 | testNoErr(params, [][]const u8 { "command", "-c100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 345 | testNoErr(params, [][]const u8 { "command", "-c", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 346 | testNoErr(params, [][]const u8 { "command", "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); | ||
| 347 | testNoErr(params, [][]const u8 { "command", "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); | ||
| 348 | testNoErr(params, [][]const u8 { "command", "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); | ||
| 349 | } | ||
| 350 | |||
| 351 | test "clap.parse: long" { | ||
| 352 | const params = []Param(u8) { | ||
| 353 | Param(u8).init(0, "aa", false), | ||
| 354 | Param(u8).init(1, "bb", false), | ||
| 355 | Param(u8).init(2, "cc", true), | ||
| 356 | }; | ||
| 357 | |||
| 358 | testNoErr(params, [][]const u8 { "command", "--aa" }, []u8{0}, []?[]const u8{null}); | ||
| 359 | testNoErr(params, [][]const u8 { "command", "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null}); | ||
| 360 | testNoErr(params, [][]const u8 { "command", "--cc=100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 361 | testNoErr(params, [][]const u8 { "command", "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 362 | } | ||
| 363 | |||
| 364 | test "clap.parse: both" { | ||
| 365 | const params = []Param(u8) { | ||
| 366 | Param(u8).both(0, 'a', "aa", false), | ||
| 367 | Param(u8).both(1, 'b', "bb", false), | ||
| 368 | Param(u8).both(2, 'c', "cc", true), | ||
| 369 | }; | ||
| 370 | |||
| 371 | testNoErr(params, [][]const u8 { "command", "-a" }, []u8{0}, []?[]const u8{null}); | ||
| 372 | testNoErr(params, [][]const u8 { "command", "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null}); | ||
| 373 | testNoErr(params, [][]const u8 { "command", "-ab" }, []u8{0,1}, []?[]const u8{null,null}); | ||
| 374 | testNoErr(params, [][]const u8 { "command", "-c=100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 375 | testNoErr(params, [][]const u8 { "command", "-c100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 376 | testNoErr(params, [][]const u8 { "command", "-c", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 377 | testNoErr(params, [][]const u8 { "command", "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); | ||
| 378 | testNoErr(params, [][]const u8 { "command", "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); | ||
| 379 | testNoErr(params, [][]const u8 { "command", "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); | ||
| 380 | testNoErr(params, [][]const u8 { "command", "--aa" }, []u8{0}, []?[]const u8{null}); | ||
| 381 | testNoErr(params, [][]const u8 { "command", "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null}); | ||
| 382 | testNoErr(params, [][]const u8 { "command", "--cc=100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 383 | testNoErr(params, [][]const u8 { "command", "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); | ||
| 384 | } | ||