summaryrefslogtreecommitdiff
path: root/clap
diff options
context:
space:
mode:
Diffstat (limited to 'clap')
-rw-r--r--clap/args.zig71
-rw-r--r--clap/comptime.zig143
-rw-r--r--clap/streaming.zig348
3 files changed, 562 insertions, 0 deletions
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 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const debug = std.debug;
5const heap = std.heap;
6const mem = std.mem;
7const process = std.process;
8
9/// An example of what methods should be implemented on an arg iterator.
10pub const ExampleArgIterator = struct {
11 const Error = error{};
12
13 pub fn next(iter: *ExampleArgIterator) Error!?[]const u8 {
14 return "2";
15 }
16};
17
18/// An argument iterator which iterates over a slice of arguments.
19/// This implementation does not allocate.
20pub const SliceIterator = struct {
21 const Error = error{};
22
23 args: []const []const u8,
24 index: usize = 0,
25
26 pub fn next(iter: *SliceIterator) Error!?[]const u8 {
27 if (iter.args.len <= iter.index)
28 return null;
29
30 defer iter.index += 1;
31 return iter.args[iter.index];
32 }
33};
34
35test "clap.args.SliceIterator" {
36 const args = [_][]const u8{ "A", "BB", "CCC" };
37 var iter = SliceIterator{ .args = args };
38
39 for (args) |a| {
40 const b = try iter.next();
41 debug.assert(mem.eql(u8, a, b.?));
42 }
43}
44
45/// An argument iterator which wraps the ArgIterator in ::std.
46/// On windows, this iterator allocates.
47pub const OsIterator = struct {
48 const Error = process.ArgIterator.NextError;
49
50 arena: heap.ArenaAllocator,
51 args: process.ArgIterator,
52
53 pub fn init(allocator: *mem.Allocator) OsIterator {
54 return OsIterator{
55 .arena = heap.ArenaAllocator.init(allocator),
56 .args = process.args(),
57 };
58 }
59
60 pub fn deinit(iter: *OsIterator) void {
61 iter.arena.deinit();
62 }
63
64 pub fn next(iter: *OsIterator) Error!?[]const u8 {
65 if (builtin.os == builtin.Os.windows) {
66 return try iter.args.next(&iter.arena.allocator) orelse return null;
67 } else {
68 return iter.args.nextPosix();
69 }
70 }
71};
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 @@
1const clap = @import("../clap.zig");
2const std = @import("std");
3
4const testing = std.testing;
5const heap = std.heap;
6const mem = std.mem;
7const debug = std.debug;
8
9pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type {
10 var flags: usize = 0;
11 var options: usize = 0;
12 var converted_params: []const clap.Param(usize) = [_]clap.Param(usize){};
13 for (params) |param| {
14 var index: usize = 0;
15 if (param.names.long != null or param.names.short != null) {
16 const ptr = if (param.takes_value) &options else &flags;
17 index = ptr.*;
18 ptr.* += 1;
19 }
20
21 const converted = clap.Param(usize){
22 .id = index,
23 .names = param.names,
24 .takes_value = param.takes_value,
25 };
26 converted_params = converted_params ++ [_]clap.Param(usize){converted};
27 }
28
29 return struct {
30 options: [options]?[]const u8,
31 flags: [flags]bool,
32 pos: []const []const u8,
33 allocator: *mem.Allocator,
34
35 pub fn parse(allocator: *mem.Allocator, comptime ArgIter: type, iter: *ArgIter) !@This() {
36 var pos = std.ArrayList([]const u8).init(allocator);
37 var res = @This(){
38 .options = [_]?[]const u8{null} ** options,
39 .flags = [_]bool{false} ** flags,
40 .pos = undefined,
41 .allocator = allocator,
42 };
43
44 var stream = clap.StreamingClap(usize, ArgIter){
45 .params = converted_params,
46 .iter = iter,
47 };
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 // If we don't have any optional parameters, then this code should
54 // never be reached.
55 debug.assert(res.options.len != 0);
56
57 // Hack: Utilize Zigs lazy analyzis to avoid a compiler error
58 if (res.options.len != 0)
59 res.options[param.id] = arg.value.?;
60 } else {
61 debug.assert(res.flags.len != 0);
62 if (res.flags.len != 0)
63 res.flags[param.id] = true;
64 }
65 }
66
67 res.pos = pos.toOwnedSlice();
68 return res;
69 }
70
71 pub fn deinit(parser: *@This()) void {
72 parser.allocator.free(parser.pos);
73 parser.* = undefined;
74 }
75
76 pub fn flag(parser: @This(), comptime name: []const u8) bool {
77 const param = comptime findParam(name);
78 if (param.takes_value)
79 @compileError(name ++ " is an option and not a flag.");
80
81 return parser.flags[param.id];
82 }
83
84 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
85 const param = comptime findParam(name);
86 if (!param.takes_value)
87 @compileError(name ++ " is a flag and not an option.");
88
89 return parser.options[param.id];
90 }
91
92 pub fn positionals(parser: @This()) []const []const u8 {
93 return parser.pos;
94 }
95
96 fn findParam(comptime name: []const u8) clap.Param(usize) {
97 comptime {
98 for (converted_params) |param| {
99 if (param.names.short) |s| {
100 if (mem.eql(u8, name, "-" ++ [_]u8{s}))
101 return param;
102 }
103 if (param.names.long) |l| {
104 if (mem.eql(u8, name, "--" ++ l))
105 return param;
106 }
107 }
108
109 @compileError(name ++ " is not a parameter.");
110 }
111 }
112 };
113}
114
115test "clap.comptime.ComptimeClap" {
116 const Clap = ComptimeClap(clap.Help, comptime [_]clap.Param(clap.Help){
117 clap.parseParam("-a, --aa ") catch unreachable,
118 clap.parseParam("-b, --bb ") catch unreachable,
119 clap.parseParam("-c, --cc <V>") catch unreachable,
120 clap.Param(clap.Help){
121 .takes_value = true,
122 },
123 });
124
125 var buf: [1024]u8 = undefined;
126 var fb_allocator = heap.FixedBufferAllocator.init(buf[0..]);
127 var iter = clap.args.SliceIterator{
128 .args = [_][]const u8{
129 "-a", "-c", "0", "something",
130 },
131 };
132 var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator, &iter);
133 defer args.deinit();
134
135 testing.expect(args.flag("-a"));
136 testing.expect(args.flag("--aa"));
137 testing.expect(!args.flag("-b"));
138 testing.expect(!args.flag("--bb"));
139 testing.expectEqualSlices(u8, "0", args.option("-c").?);
140 testing.expectEqualSlices(u8, "0", args.option("--cc").?);
141 testing.expectEqual(usize(1), args.positionals().len);
142 testing.expectEqualSlices(u8, "something", args.positionals()[0]);
143}
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 @@
1const builtin = @import("builtin");
2const clap = @import("../clap.zig");
3const std = @import("std");
4
5const args = clap.args;
6const testing = std.testing;
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 = null,
18 };
19}
20
21/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
22/// to the ::params. ::StreamingClap parses in an iterating manner, so you have to use a loop together with
23/// ::StreamingClap.next to parse all the arguments of your program.
24pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
25 return struct {
26 const State = union(enum) {
27 Normal,
28 Chaining: Chaining,
29
30 const Chaining = struct {
31 arg: []const u8,
32 index: usize,
33 };
34 };
35
36 params: []const clap.Param(Id),
37 iter: *ArgIterator,
38 state: State = State.Normal,
39
40 /// Get the next ::Arg that matches a ::Param.
41 pub fn next(parser: *@This()) !?Arg(Id) {
42 const ArgInfo = struct {
43 const Kind = enum {
44 Long,
45 Short,
46 Positional,
47 };
48
49 arg: []const u8,
50 kind: Kind,
51 };
52
53 switch (parser.state) {
54 .Normal => {
55 const full_arg = (try parser.iter.next()) orelse return null;
56 const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-"))
57 ArgInfo{ .arg = full_arg, .kind = .Positional }
58 else if (mem.startsWith(u8, full_arg, "--"))
59 ArgInfo{ .arg = full_arg[2..], .kind = .Long }
60 else if (mem.startsWith(u8, full_arg, "-"))
61 ArgInfo{ .arg = full_arg[1..], .kind = .Short }
62 else
63 ArgInfo{ .arg = full_arg, .kind = .Positional };
64
65 const arg = arg_info.arg;
66 const kind = arg_info.kind;
67 const eql_index = mem.indexOfScalar(u8, arg, '=');
68
69 switch (kind) {
70 ArgInfo.Kind.Long => {
71 for (parser.params) |*param| {
72 const match = param.names.long orelse continue;
73 const name = if (eql_index) |i| arg[0..i] else arg;
74 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
75
76 if (!mem.eql(u8, name, match))
77 continue;
78 if (!param.takes_value) {
79 if (maybe_value != null)
80 return error.DoesntTakeValue;
81
82 return Arg(Id){ .param = param };
83 }
84
85 const value = blk: {
86 if (maybe_value) |v|
87 break :blk v;
88
89 break :blk (try parser.iter.next()) orelse return error.MissingValue;
90 };
91
92 return Arg(Id){ .param = param, .value = value };
93 }
94 },
95 ArgInfo.Kind.Short => {
96 return try parser.chainging(State.Chaining{
97 .arg = full_arg,
98 .index = (full_arg.len - arg.len),
99 });
100 },
101 ArgInfo.Kind.Positional => {
102 for (parser.params) |*param| {
103 if (param.names.long) |_|
104 continue;
105 if (param.names.short) |_|
106 continue;
107
108 return Arg(Id){ .param = param, .value = arg };
109 }
110 },
111 }
112
113 return error.InvalidArgument;
114 },
115 .Chaining => |state| return try parser.chainging(state),
116 }
117 }
118
119 fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) {
120 const arg = state.arg;
121 const index = state.index;
122 const next_index = index + 1;
123
124 for (parser.params) |*param| {
125 const short = param.names.short orelse continue;
126 if (short != arg[index])
127 continue;
128
129 // Before we return, we have to set the new state of the clap
130 defer {
131 if (arg.len <= next_index or param.takes_value) {
132 parser.state = State.Normal;
133 } else {
134 parser.state = State{
135 .Chaining = State.Chaining{
136 .arg = arg,
137 .index = next_index,
138 },
139 };
140 }
141 }
142
143 if (!param.takes_value)
144 return Arg(Id){ .param = param };
145
146 if (arg.len <= next_index) {
147 const value = (try parser.iter.next()) orelse return error.MissingValue;
148 return Arg(Id){ .param = param, .value = value };
149 }
150
151 if (arg[next_index] == '=')
152 return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] };
153
154 return Arg(Id){ .param = param, .value = arg[next_index..] };
155 }
156
157 return error.InvalidArgument;
158 }
159 };
160}
161
162fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void {
163 var iter = args.SliceIterator{ .args = args_strings };
164 var c = StreamingClap(u8, args.SliceIterator){
165 .params = params,
166 .iter = &iter,
167 };
168
169 for (results) |res| {
170 const arg = (c.next() catch unreachable) orelse unreachable;
171 testing.expectEqual(res.param, arg.param);
172 const expected_value = res.value orelse {
173 testing.expectEqual(@typeOf(arg.value)(null), arg.value);
174 continue;
175 };
176 const actual_value = arg.value orelse unreachable;
177 testing.expectEqualSlices(u8, expected_value, actual_value);
178 }
179
180 if (c.next() catch unreachable) |_|
181 unreachable;
182}
183
184test "clap.streaming.StreamingClap: short params" {
185 const params = [_]clap.Param(u8){
186 clap.Param(u8){
187 .id = 0,
188 .names = clap.Names{ .short = 'a' },
189 },
190 clap.Param(u8){
191 .id = 1,
192 .names = clap.Names{ .short = 'b' },
193 },
194 clap.Param(u8){
195 .id = 2,
196 .names = clap.Names{ .short = 'c' },
197 .takes_value = true,
198 },
199 };
200
201 const a = &params[0];
202 const b = &params[1];
203 const c = &params[2];
204
205 testNoErr(
206 params,
207 [_][]const u8{
208 "-a", "-b", "-ab", "-ba",
209 "-c", "0", "-c=0", "-ac",
210 "0", "-ac=0",
211 },
212 [_]Arg(u8){
213 Arg(u8){ .param = a },
214 Arg(u8){ .param = b },
215 Arg(u8){ .param = a },
216 Arg(u8){ .param = b },
217 Arg(u8){ .param = b },
218 Arg(u8){ .param = a },
219 Arg(u8){ .param = c, .value = "0" },
220 Arg(u8){ .param = c, .value = "0" },
221 Arg(u8){ .param = a },
222 Arg(u8){ .param = c, .value = "0" },
223 Arg(u8){ .param = a },
224 Arg(u8){ .param = c, .value = "0" },
225 },
226 );
227}
228
229test "clap.streaming.StreamingClap: long params" {
230 const params = [_]clap.Param(u8){
231 clap.Param(u8){
232 .id = 0,
233 .names = clap.Names{ .long = "aa" },
234 },
235 clap.Param(u8){
236 .id = 1,
237 .names = clap.Names{ .long = "bb" },
238 },
239 clap.Param(u8){
240 .id = 2,
241 .names = clap.Names{ .long = "cc" },
242 .takes_value = true,
243 },
244 };
245
246 const aa = &params[0];
247 const bb = &params[1];
248 const cc = &params[2];
249
250 testNoErr(
251 params,
252 [_][]const u8{
253 "--aa", "--bb",
254 "--cc", "0",
255 "--cc=0",
256 },
257 [_]Arg(u8){
258 Arg(u8){ .param = aa },
259 Arg(u8){ .param = bb },
260 Arg(u8){ .param = cc, .value = "0" },
261 Arg(u8){ .param = cc, .value = "0" },
262 },
263 );
264}
265
266test "clap.streaming.StreamingClap: positional params" {
267 const params = [_]clap.Param(u8){clap.Param(u8){
268 .id = 0,
269 .takes_value = true,
270 }};
271
272 testNoErr(
273 params,
274 [_][]const u8{ "aa", "bb" },
275 [_]Arg(u8){
276 Arg(u8){ .param = &params[0], .value = "aa" },
277 Arg(u8){ .param = &params[0], .value = "bb" },
278 },
279 );
280}
281
282test "clap.streaming.StreamingClap: all params" {
283 const params = [_]clap.Param(u8){
284 clap.Param(u8){
285 .id = 0,
286 .names = clap.Names{
287 .short = 'a',
288 .long = "aa",
289 },
290 },
291 clap.Param(u8){
292 .id = 1,
293 .names = clap.Names{
294 .short = 'b',
295 .long = "bb",
296 },
297 },
298 clap.Param(u8){
299 .id = 2,
300 .names = clap.Names{
301 .short = 'c',
302 .long = "cc",
303 },
304 .takes_value = true,
305 },
306 clap.Param(u8){
307 .id = 3,
308 .takes_value = true,
309 },
310 };
311
312 const aa = &params[0];
313 const bb = &params[1];
314 const cc = &params[2];
315 const positional = &params[3];
316
317 testNoErr(
318 params,
319 [_][]const u8{
320 "-a", "-b", "-ab", "-ba",
321 "-c", "0", "-c=0", "-ac",
322 "0", "-ac=0", "--aa", "--bb",
323 "--cc", "0", "--cc=0", "something",
324 "--", "-",
325 },
326 [_]Arg(u8){
327 Arg(u8){ .param = aa },
328 Arg(u8){ .param = bb },
329 Arg(u8){ .param = aa },
330 Arg(u8){ .param = bb },
331 Arg(u8){ .param = bb },
332 Arg(u8){ .param = aa },
333 Arg(u8){ .param = cc, .value = "0" },
334 Arg(u8){ .param = cc, .value = "0" },
335 Arg(u8){ .param = aa },
336 Arg(u8){ .param = cc, .value = "0" },
337 Arg(u8){ .param = aa },
338 Arg(u8){ .param = cc, .value = "0" },
339 Arg(u8){ .param = aa },
340 Arg(u8){ .param = bb },
341 Arg(u8){ .param = cc, .value = "0" },
342 Arg(u8){ .param = cc, .value = "0" },
343 Arg(u8){ .param = positional, .value = "something" },
344 Arg(u8){ .param = positional, .value = "--" },
345 Arg(u8){ .param = positional, .value = "-" },
346 },
347 );
348}