summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core.zig217
1 files changed, 185 insertions, 32 deletions
diff --git a/core.zig b/core.zig
index 1839171..d9dc804 100644
--- a/core.zig
+++ b/core.zig
@@ -3,7 +3,8 @@ const builtin = @import("builtin");
3 3
4const os = std.os; 4const os = std.os;
5const heap = std.heap; 5const heap = std.heap;
6const is_windows = builtin.os == Os.windows; 6const mem = std.mem;
7const 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.
27pub fn Param(comptime Id: type) type { 28pub 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
86pub 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
96pub 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
121pub 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.
77pub fn Iterator(comptime Id: type) type { 145pub 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
316fn 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
333test "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
351test "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
364test "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}