summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2018-11-14 14:06:20 +0100
committerGravatar Jimmi Holst Christensen2018-11-14 14:06:20 +0100
commitc564b168785e740c37f47da4942825b25cb8b4ec (patch)
tree880252c7a63bdc91f23ba5e13b593d4ca9ad8277 /src
parentZig fmt (diff)
downloadzig-clap-c564b168785e740c37f47da4942825b25cb8b4ec.tar.gz
zig-clap-c564b168785e740c37f47da4942825b25cb8b4ec.tar.xz
zig-clap-c564b168785e740c37f47da4942825b25cb8b4ec.zip
Restructured and make StreamingClap simpler
* Also added a ComptimeClap
Diffstat (limited to '')
-rw-r--r--src/args.zig90
-rw-r--r--src/comptime.zig142
-rw-r--r--src/index.zig105
-rw-r--r--src/streaming.zig338
4 files changed, 675 insertions, 0 deletions
diff --git a/src/args.zig b/src/args.zig
new file mode 100644
index 0000000..304596c
--- /dev/null
+++ b/src/args.zig
@@ -0,0 +1,90 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const debug = std.debug;
5const heap = std.heap;
6const mem = std.mem;
7const os = std.os;
8
9/// A interface for iterating over command line arguments
10pub fn Iterator(comptime E: type) type {
11 return struct {
12 const Self = @This();
13 const Error = E;
14
15 nextFn: fn (iter: *Self) Error!?[]const u8,
16
17 pub fn next(iter: *Self) Error!?[]const u8 {
18 return iter.nextFn(iter);
19 }
20 };
21}
22
23/// An ::ArgIterator, which iterates over a slice of arguments.
24/// This implementation does not allocate.
25pub const SliceIterator = struct {
26 const Error = error{};
27
28 args: []const []const u8,
29 index: usize,
30 iter: Iterator(Error),
31
32 pub fn init(args: []const []const u8) SliceIterator {
33 return SliceIterator{
34 .args = args,
35 .index = 0,
36 .iter = Iterator(Error){ .nextFn = nextFn },
37 };
38 }
39
40 fn nextFn(iter: *Iterator(Error)) Error!?[]const u8 {
41 const self = @fieldParentPtr(SliceIterator, "iter", iter);
42 if (self.args.len <= self.index)
43 return null;
44
45 defer self.index += 1;
46 return self.args[self.index];
47 }
48};
49
50test "clap.args.SliceIterator" {
51 const args = [][]const u8{ "A", "BB", "CCC" };
52 var slice_iter = SliceIterator.init(args);
53 const iter = &slice_iter.iter;
54
55 for (args) |a| {
56 const b = try iter.next();
57 debug.assert(mem.eql(u8, a, b.?));
58 }
59}
60
61/// An ::ArgIterator, which wraps the ArgIterator in ::std.
62/// On windows, this iterator allocates.
63pub const OsIterator = struct {
64 const Error = os.ArgIterator.NextError;
65
66 arena: heap.ArenaAllocator,
67 args: os.ArgIterator,
68 iter: Iterator(Error),
69
70 pub fn init(allocator: *mem.Allocator) OsIterator {
71 return OsIterator{
72 .arena = heap.ArenaAllocator.init(allocator),
73 .args = os.args(),
74 .iter = Iterator(Error){ .nextFn = nextFn },
75 };
76 }
77
78 pub fn deinit(iter: *OsIterator) void {
79 iter.arena.deinit();
80 }
81
82 fn nextFn(iter: *Iterator(Error)) Error!?[]const u8 {
83 const self = @fieldParentPtr(OsIterator, "iter", iter);
84 if (builtin.os == builtin.Os.windows) {
85 return try self.args.next(&self.arena.allocator) orelse return null;
86 } else {
87 return self.args.nextPosix();
88 }
89 }
90};
diff --git a/src/comptime.zig b/src/comptime.zig
new file mode 100644
index 0000000..b11dccc
--- /dev/null
+++ b/src/comptime.zig
@@ -0,0 +1,142 @@
1const clap = @import("index.zig");
2const std = @import("std");
3
4const debug = std.debug;
5const heap = std.heap;
6const mem = std.mem;
7
8pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type {
9 var flags: usize = 0;
10 var options: usize = 0;
11 var converted_params: []const clap.Param(usize) = []clap.Param(usize){};
12 for (params) |param| {
13 const index = blk: {
14 if (param.names.long == null and param.names.short == null)
15 break :blk 0;
16 if (param.takes_value) {
17 const res = options;
18 options += 1;
19 break :blk res;
20 }
21
22 const res = flags;
23 flags += 1;
24 break :blk res;
25 };
26
27 converted_params = converted_params ++ []clap.Param(usize){
28 clap.Param(usize).init(index, param.takes_value, param.names),
29 };
30 }
31
32 return struct {
33 options: [options]?[]const u8,
34 flags: [flags]bool,
35 pos: []const []const u8,
36 allocator: *mem.Allocator,
37
38 pub fn parse(allocator: *mem.Allocator, comptime ArgError: type, iter: *clap.args.Iterator(ArgError)) !@This() {
39 var pos = std.ArrayList([]const u8).init(allocator);
40 var res = @This(){
41 .options = []?[]const u8{null} ** options,
42 .flags = []bool{false} ** flags,
43 .pos = undefined,
44 .allocator = allocator,
45 };
46
47 var stream = clap.StreamingClap(usize, ArgError).init(converted_params, iter);
48 while (try stream.next()) |arg| {
49 const param = arg.param;
50 if (param.names.long == null and param.names.short == null) {
51 try pos.append(arg.value.?);
52 } else if (param.takes_value) {
53 // We slice before access to avoid false positive access out of bound
54 // compile error.
55 res.options[0..][param.id] = arg.value.?;
56 } else {
57 res.flags[0..][param.id] = true;
58 }
59 }
60
61 res.pos = pos.toOwnedSlice();
62 return res;
63 }
64
65 pub fn deinit(parser: *@This()) void {
66 parser.allocator.free(parser.pos);
67 parser.* = undefined;
68 }
69
70 pub fn flag(parser: @This(), comptime name: []const u8) bool {
71 const param = comptime findParam(name);
72 if (param.takes_value)
73 @compileError(name ++ " is an option and not a flag.");
74
75 return parser.flags[param.id];
76 }
77
78 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
79 const param = comptime findParam(name);
80 if (!param.takes_value)
81 @compileError(name ++ " is a flag and not an option.");
82
83 return parser.options[param.id];
84 }
85
86 pub fn positionals(parser: @This()) []const []const u8 {
87 return parser.pos;
88 }
89
90 fn findParam(comptime name: []const u8) clap.Param(usize) {
91 comptime {
92 for (converted_params) |param| {
93 if (param.names.short) |s| {
94 if (mem.eql(u8, name, "-" ++ []u8{s}))
95 return param;
96 }
97 if (param.names.long) |l| {
98 if (mem.eql(u8, name, "--" ++ l))
99 return param;
100 }
101 }
102
103 @compileError(name ++ " is not a parameter.");
104 }
105 }
106 };
107}
108
109test "clap.comptime.ComptimeClap" {
110 const Clap = ComptimeClap(void, comptime []clap.Param(void){
111 clap.Param(void).init({}, false, clap.Names{
112 .short = 'a',
113 .long = "aa",
114 }),
115 clap.Param(void).init({}, false, clap.Names{
116 .short = 'b',
117 .long = "bb",
118 }),
119 clap.Param(void).init({}, true, clap.Names{
120 .short = 'c',
121 .long = "cc",
122 }),
123 clap.Param(void).init({}, true, clap.Names.positional()),
124 });
125
126 var buf: [1024]u8 = undefined;
127 var fb_allocator = heap.FixedBufferAllocator.init(buf[0..]);
128 var arg_iter = clap.args.SliceIterator.init([][]const u8{
129 "-a", "-c", "0", "something",
130 });
131 var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator.Error, &arg_iter.iter);
132 defer args.deinit();
133
134 debug.assert(args.flag("-a"));
135 debug.assert(args.flag("--aa"));
136 debug.assert(!args.flag("-b"));
137 debug.assert(!args.flag("--bb"));
138 debug.assert(mem.eql(u8, args.option("-c").?, "0"));
139 debug.assert(mem.eql(u8, args.option("--cc").?, "0"));
140 debug.assert(args.positionals().len == 1);
141 debug.assert(mem.eql(u8, args.positionals()[0], "something"));
142}
diff --git a/src/index.zig b/src/index.zig
new file mode 100644
index 0000000..dde4748
--- /dev/null
+++ b/src/index.zig
@@ -0,0 +1,105 @@
1const std = @import("std");
2
3const debug = std.debug;
4
5pub const @"comptime" = @import("comptime.zig");
6pub const args = @import("args.zig");
7pub const streaming = @import("streaming.zig");
8
9test "clap" {
10 _ = @"comptime";
11 _ = args;
12 _ = streaming;
13}
14
15pub const ComptimeClap = @"comptime".ComptimeClap;
16pub const StreamingClap = streaming.StreamingClap;
17
18/// The names a ::Param can have.
19pub const Names = struct {
20 /// '-' prefix
21 short: ?u8,
22
23 /// '--' prefix
24 long: ?[]const u8,
25
26 /// Initializes no names
27 pub fn positional() Names {
28 return Names{
29 .short = null,
30 .long = null,
31 };
32 }
33
34 /// Initializes a short name
35 pub fn short(s: u8) Names {
36 return Names{
37 .short = s,
38 .long = null,
39 };
40 }
41
42 /// Initializes a long name
43 pub fn long(l: []const u8) Names {
44 return Names{
45 .short = null,
46 .long = l,
47 };
48 }
49
50 /// Initializes a name with a prefix.
51 /// ::short is set to ::name[0], and ::long is set to ::name.
52 /// This function asserts that ::name.len != 0
53 pub fn prefix(name: []const u8) Names {
54 debug.assert(name.len != 0);
55
56 return Names{
57 .short = name[0],
58 .long = name,
59 };
60 }
61};
62
63/// Represents a parameter for the command line.
64/// Parameters come in three kinds:
65/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
66/// * They can take a value three different ways.
67/// * "-a value"
68/// * "-a=value"
69/// * "-avalue"
70/// * They chain if they don't take values: "-abc".
71/// * The last given parameter can take a value in the same way that a single parameter can:
72/// * "-abc value"
73/// * "-abc=value"
74/// * "-abcvalue"
75/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
76/// can describe the paramter.
77/// * They can take a value two different ways.
78/// * "--long-param value"
79/// * "--long-param=value"
80/// * Positional: Should be used as the primary parameter of the program, like a filename or
81/// an expression to parse.
82/// * Positional parameters have both names.long and names.short == null.
83/// * Positional parameters must take a value.
84pub fn Param(comptime Id: type) type {
85 return struct {
86 id: Id,
87 takes_value: bool,
88 names: Names,
89
90 pub fn init(id: Id, takes_value: bool, names: Names) @This() {
91 // Assert, that if the param have no name, then it has to take
92 // a value.
93 debug.assert(
94 names.long != null or
95 names.short != null or
96 takes_value);
97
98 return @This(){
99 .id = id,
100 .takes_value = takes_value,
101 .names = names,
102 };
103 }
104 };
105}
diff --git a/src/streaming.zig b/src/streaming.zig
new file mode 100644
index 0000000..bfb4045
--- /dev/null
+++ b/src/streaming.zig
@@ -0,0 +1,338 @@
1const builtin = @import("builtin");
2const clap = @import("index.zig");
3const std = @import("std");
4
5const args = clap.args;
6const debug = std.debug;
7const heap = std.heap;
8const mem = std.mem;
9const os = std.os;
10
11/// The result returned from ::StreamingClap.next
12pub fn Arg(comptime Id: type) type {
13 return struct {
14 const Self = @This();
15
16 param: *const clap.Param(Id),
17 value: ?[]const u8,
18
19 pub fn init(param: *const clap.Param(Id), value: ?[]const u8) Self {
20 return Self{
21 .param = param,
22 .value = value,
23 };
24 }
25 };
26}
27
28/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
29/// to the ::params. ::StreamingClap parses in an iterating manner, so you have to use a loop together with
30/// ::StreamingClap.next to parse all the arguments of your program.
31pub fn StreamingClap(comptime Id: type, comptime ArgError: type) type {
32 return struct {
33
34 const State = union(enum) {
35 Normal,
36 Chaining: Chaining,
37
38 const Chaining = struct {
39 arg: []const u8,
40 index: usize,
41 };
42 };
43
44 params: []const clap.Param(Id),
45 iter: *args.Iterator(ArgError),
46 state: State,
47
48 pub fn init(params: []const clap.Param(Id), iter: *args.Iterator(ArgError)) @This() {
49 var res = @This(){
50 .params = params,
51 .iter = iter,
52 .state = State.Normal,
53 };
54
55 return res;
56 }
57
58 /// Get the next ::Arg that matches a ::Param.
59 pub fn next(parser: *@This()) !?Arg(Id) {
60 const ArgInfo = struct {
61 const Kind = enum {
62 Long,
63 Short,
64 Positional,
65 };
66
67 arg: []const u8,
68 kind: Kind,
69 };
70
71 switch (parser.state) {
72 State.Normal => {
73 const full_arg = (try parser.iter.next()) orelse return null;
74 const arg_info = blk: {
75 var arg = full_arg;
76 var kind = ArgInfo.Kind.Positional;
77
78 if (mem.startsWith(u8, arg, "--")) {
79 arg = arg[2..];
80 kind = ArgInfo.Kind.Long;
81 } else if (mem.startsWith(u8, arg, "-")) {
82 arg = arg[1..];
83 kind = ArgInfo.Kind.Short;
84 }
85
86 // We allow long arguments to go without a name.
87 // This allows the user to use "--" for something important
88 if (kind != ArgInfo.Kind.Long and arg.len == 0)
89 return error.InvalidArgument;
90
91 break :blk ArgInfo{ .arg = arg, .kind = kind };
92 };
93
94 const arg = arg_info.arg;
95 const kind = arg_info.kind;
96 const eql_index = mem.indexOfScalar(u8, arg, '=');
97
98 switch (kind) {
99 ArgInfo.Kind.Long => {
100 for (parser.params) |*param| {
101 const match = param.names.long orelse continue;
102 const name = if (eql_index) |i| arg[0..i] else arg;
103 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
104
105 if (!mem.eql(u8, name, match))
106 continue;
107 if (!param.takes_value) {
108 if (maybe_value != null)
109 return error.DoesntTakeValue;
110
111 return Arg(Id).init(param, null);
112 }
113
114 const value = blk: {
115 if (maybe_value) |v|
116 break :blk v;
117
118 break :blk (try parser.iter.next()) orelse return error.MissingValue;
119 };
120
121 return Arg(Id).init(param, value);
122 }
123 },
124 ArgInfo.Kind.Short => {
125 return try parser.chainging(State.Chaining{
126 .arg = full_arg,
127 .index = (full_arg.len - arg.len),
128 });
129 },
130 ArgInfo.Kind.Positional => {
131 for (parser.params) |*param| {
132 if (param.names.long) |_|
133 continue;
134 if (param.names.short) |_|
135 continue;
136
137 return Arg(Id).init(param, arg);
138 }
139 },
140 }
141
142 return error.InvalidArgument;
143 },
144 @TagType(State).Chaining => |state| return try parser.chainging(state),
145 }
146 }
147
148 fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) {
149 const arg = state.arg;
150 const index = state.index;
151 const next_index = index + 1;
152
153 for (parser.params) |*param| {
154 const short = param.names.short orelse continue;
155 if (short != arg[index])
156 continue;
157
158 // Before we return, we have to set the new state of the clap
159 defer {
160 if (arg.len <= next_index or param.takes_value) {
161 parser.state = State.Normal;
162 } else {
163 parser.state = State{
164 .Chaining = State.Chaining{
165 .arg = arg,
166 .index = next_index,
167 },
168 };
169 }
170 }
171
172 if (!param.takes_value)
173 return Arg(Id).init(param, null);
174
175 if (arg.len <= next_index) {
176 const value = (try parser.iter.next()) orelse return error.MissingValue;
177 return Arg(Id).init(param, value);
178 }
179
180 if (arg[next_index] == '=') {
181 return Arg(Id).init(param, arg[next_index + 1 ..]);
182 }
183
184 return Arg(Id).init(param, arg[next_index..]);
185 }
186
187 return error.InvalidArgument;
188 }
189 };
190}
191
192
193fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void {
194 var arg_iter = args.SliceIterator.init(args_strings);
195 var c = StreamingClap(u8, args.SliceIterator.Error).init(params, &arg_iter.iter);
196
197 for (results) |res| {
198 const arg = (c.next() catch unreachable) orelse unreachable;
199 debug.assert(res.param == arg.param);
200 const expected_value = res.value orelse {
201 debug.assert(arg.value == null);
202 continue;
203 };
204 const actual_value = arg.value orelse unreachable;
205 debug.assert(mem.eql(u8, expected_value, actual_value));
206 }
207
208 if (c.next() catch unreachable) |_| {
209 unreachable;
210 }
211}
212
213test "clap.streaming.StreamingClap: short params" {
214 const params = []clap.Param(u8){
215 clap.Param(u8).init(0, false, clap.Names.short('a')),
216 clap.Param(u8).init(1, false, clap.Names.short('b')),
217 clap.Param(u8).init(2, true, clap.Names.short('c')),
218 };
219
220 const a = &params[0];
221 const b = &params[1];
222 const c = &params[2];
223
224 testNoErr(
225 params,
226 [][]const u8{
227 "-a", "-b", "-ab", "-ba",
228 "-c", "0", "-c=0", "-ac",
229 "0", "-ac=0",
230 },
231 []const Arg(u8){
232 Arg(u8).init(a, null),
233 Arg(u8).init(b, null),
234 Arg(u8).init(a, null),
235 Arg(u8).init(b, null),
236 Arg(u8).init(b, null),
237 Arg(u8).init(a, null),
238 Arg(u8).init(c, "0"),
239 Arg(u8).init(c, "0"),
240 Arg(u8).init(a, null),
241 Arg(u8).init(c, "0"),
242 Arg(u8).init(a, null),
243 Arg(u8).init(c, "0"),
244 },
245 );
246}
247
248test "clap.streaming.StreamingClap: long params" {
249 const params = []clap.Param(u8){
250 clap.Param(u8).init(0, false, clap.Names.long("aa")),
251 clap.Param(u8).init(1, false, clap.Names.long("bb")),
252 clap.Param(u8).init(2, true, clap.Names.long("cc")),
253 };
254
255 const aa = &params[0];
256 const bb = &params[1];
257 const cc = &params[2];
258
259 testNoErr(
260 params,
261 [][]const u8{
262 "--aa", "--bb",
263 "--cc", "0",
264 "--cc=0",
265 },
266 []const Arg(u8){
267 Arg(u8).init(aa, null),
268 Arg(u8).init(bb, null),
269 Arg(u8).init(cc, "0"),
270 Arg(u8).init(cc, "0"),
271 },
272 );
273}
274
275test "clap.streaming.StreamingClap: positional params" {
276 const params = []clap.Param(u8){clap.Param(u8).init(0, true, clap.Names.positional())};
277
278 testNoErr(
279 params,
280 [][]const u8{ "aa", "bb" },
281 []const Arg(u8){
282 Arg(u8).init(&params[0], "aa"),
283 Arg(u8).init(&params[0], "bb"),
284 },
285 );
286}
287
288test "clap.streaming.StreamingClap: all params" {
289 const params = []clap.Param(u8){
290 clap.Param(u8).init(0, false, clap.Names{
291 .short = 'a',
292 .long = "aa",
293 }),
294 clap.Param(u8).init(1, false, clap.Names{
295 .short = 'b',
296 .long = "bb",
297 }),
298 clap.Param(u8).init(2, true, clap.Names{
299 .short = 'c',
300 .long = "cc",
301 }),
302 clap.Param(u8).init(3, true, clap.Names.positional()),
303 };
304
305 const aa = &params[0];
306 const bb = &params[1];
307 const cc = &params[2];
308 const positional = &params[3];
309
310 testNoErr(
311 params,
312 [][]const u8{
313 "-a", "-b", "-ab", "-ba",
314 "-c", "0", "-c=0", "-ac",
315 "0", "-ac=0", "--aa", "--bb",
316 "--cc", "0", "--cc=0", "something",
317 },
318 []const Arg(u8){
319 Arg(u8).init(aa, null),
320 Arg(u8).init(bb, null),
321 Arg(u8).init(aa, null),
322 Arg(u8).init(bb, null),
323 Arg(u8).init(bb, null),
324 Arg(u8).init(aa, null),
325 Arg(u8).init(cc, "0"),
326 Arg(u8).init(cc, "0"),
327 Arg(u8).init(aa, null),
328 Arg(u8).init(cc, "0"),
329 Arg(u8).init(aa, null),
330 Arg(u8).init(cc, "0"),
331 Arg(u8).init(aa, null),
332 Arg(u8).init(bb, null),
333 Arg(u8).init(cc, "0"),
334 Arg(u8).init(cc, "0"),
335 Arg(u8).init(positional, "something"),
336 },
337 );
338}