summaryrefslogtreecommitdiff
path: root/clap
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2022-02-25 19:40:00 +0100
committerGravatar Komari Spaghetti2022-03-09 18:12:40 +0100
commit5f7b75d5523d9581eca5a72a362868ff517992e8 (patch)
tree5e874f9c935e0d7c838ea5aadf270ce2929f8e8a /clap
parentBump actions/checkout from 2.4.0 to 3 (diff)
downloadzig-clap-5f7b75d5523d9581eca5a72a362868ff517992e8.tar.gz
zig-clap-5f7b75d5523d9581eca5a72a362868ff517992e8.tar.xz
zig-clap-5f7b75d5523d9581eca5a72a362868ff517992e8.zip
Allow for clap to parse argument values into types
This changes - `.flag`, `.option`, `.options` and `.positionals` are now just fields you access on the result of `parse` and `parseEx`. - `clap.ComptimeClap` has been removed. - `clap.StreamingClap` is now called `clap.streaming.Clap` - `parse` and `parseEx` now takes a `value_parsers` argument that provides the parsers to parse values. - Remove `helpEx`, `helpFull`, `usageEx` and `usageFull`. They now just expect `Id` to have methods for getting the description and value texts.
Diffstat (limited to 'clap')
-rw-r--r--clap/comptime.zig237
-rw-r--r--clap/parsers.zig48
-rw-r--r--clap/streaming.zig34
3 files changed, 69 insertions, 250 deletions
diff --git a/clap/comptime.zig b/clap/comptime.zig
deleted file mode 100644
index b440004..0000000
--- a/clap/comptime.zig
+++ /dev/null
@@ -1,237 +0,0 @@
1const clap = @import("../clap.zig");
2const std = @import("std");
3
4const debug = std.debug;
5const heap = std.heap;
6const io = std.io;
7const mem = std.mem;
8const testing = std.testing;
9
10/// Deprecated: Use `parseEx` instead
11pub fn ComptimeClap(
12 comptime Id: type,
13 comptime params: []const clap.Param(Id),
14) type {
15 comptime var flags: usize = 0;
16 comptime var single_options: usize = 0;
17 comptime var multi_options: usize = 0;
18 comptime var converted_params: []const clap.Param(usize) = &.{};
19 for (params) |param| {
20 var index: usize = 0;
21 if (param.names.long != null or param.names.short != null) {
22 const ptr = switch (param.takes_value) {
23 .none => &flags,
24 .one => &single_options,
25 .many => &multi_options,
26 };
27 index = ptr.*;
28 ptr.* += 1;
29 }
30
31 converted_params = converted_params ++ [_]clap.Param(usize){.{
32 .id = index,
33 .names = param.names,
34 .takes_value = param.takes_value,
35 }};
36 }
37
38 return struct {
39 multi_options: [multi_options][]const []const u8,
40 single_options: [single_options][]const u8,
41 single_options_is_set: std.PackedIntArray(u1, single_options),
42 flags: std.PackedIntArray(u1, flags),
43 pos: []const []const u8,
44 allocator: mem.Allocator,
45
46 pub fn parse(iter: anytype, opt: clap.ParseOptions) !@This() {
47 const allocator = opt.allocator;
48 var multis = [_]std.ArrayList([]const u8){undefined} ** multi_options;
49 for (multis) |*multi|
50 multi.* = std.ArrayList([]const u8).init(allocator);
51
52 var pos = std.ArrayList([]const u8).init(allocator);
53
54 var res = @This(){
55 .multi_options = .{undefined} ** multi_options,
56 .single_options = .{undefined} ** single_options,
57 .single_options_is_set = std.PackedIntArray(u1, single_options).init(
58 .{0} ** single_options,
59 ),
60 .flags = std.PackedIntArray(u1, flags).init(.{0} ** flags),
61 .pos = undefined,
62 .allocator = allocator,
63 };
64
65 var stream = clap.StreamingClap(usize, @typeInfo(@TypeOf(iter)).Pointer.child){
66 .params = converted_params,
67 .iter = iter,
68 .diagnostic = opt.diagnostic,
69 };
70 while (try stream.next()) |arg| {
71 const param = arg.param;
72 if (param.names.long == null and param.names.short == null) {
73 try pos.append(arg.value.?);
74 } else if (param.takes_value == .one) {
75 debug.assert(res.single_options.len != 0);
76 if (res.single_options.len != 0) {
77 res.single_options[param.id] = arg.value.?;
78 res.single_options_is_set.set(param.id, 1);
79 }
80 } else if (param.takes_value == .many) {
81 debug.assert(multis.len != 0);
82 if (multis.len != 0)
83 try multis[param.id].append(arg.value.?);
84 } else {
85 debug.assert(res.flags.len != 0);
86 if (res.flags.len != 0)
87 res.flags.set(param.id, 1);
88 }
89 }
90
91 for (multis) |*multi, i|
92 res.multi_options[i] = multi.toOwnedSlice();
93 res.pos = pos.toOwnedSlice();
94
95 return res;
96 }
97
98 pub fn deinit(parser: @This()) void {
99 for (parser.multi_options) |o|
100 parser.allocator.free(o);
101 parser.allocator.free(parser.pos);
102 }
103
104 pub fn flag(parser: @This(), comptime name: []const u8) bool {
105 const param = comptime findParam(name);
106 if (param.takes_value != .none)
107 @compileError(name ++ " is an option and not a flag.");
108
109 return parser.flags.get(param.id) != 0;
110 }
111
112 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
113 const param = comptime findParam(name);
114 if (param.takes_value == .none)
115 @compileError(name ++ " is a flag and not an option.");
116 if (param.takes_value == .many)
117 @compileError(name ++ " takes many options, not one.");
118 if (parser.single_options_is_set.get(param.id) == 0)
119 return null;
120 return parser.single_options[param.id];
121 }
122
123 pub fn options(parser: @This(), comptime name: []const u8) []const []const u8 {
124 const param = comptime findParam(name);
125 if (param.takes_value == .none)
126 @compileError(name ++ " is a flag and not an option.");
127 if (param.takes_value == .one)
128 @compileError(name ++ " takes one option, not multiple.");
129
130 return parser.multi_options[param.id];
131 }
132
133 pub fn positionals(parser: @This()) []const []const u8 {
134 return parser.pos;
135 }
136
137 fn findParam(comptime name: []const u8) clap.Param(usize) {
138 comptime {
139 for (converted_params) |param| {
140 if (param.names.short) |s| {
141 if (mem.eql(u8, name, "-" ++ [_]u8{s}))
142 return param;
143 }
144 if (param.names.long) |l| {
145 if (mem.eql(u8, name, "--" ++ l))
146 return param;
147 }
148 }
149
150 @compileError(name ++ " is not a parameter.");
151 }
152 }
153 };
154}
155
156test "" {
157 const params = comptime &.{
158 clap.parseParam("-a, --aa") catch unreachable,
159 clap.parseParam("-b, --bb") catch unreachable,
160 clap.parseParam("-c, --cc <V>") catch unreachable,
161 clap.parseParam("-d, --dd <V>...") catch unreachable,
162 clap.parseParam("<P>") catch unreachable,
163 };
164
165 var iter = clap.args.SliceIterator{
166 .args = &.{
167 "-a", "-c", "0", "something", "-d", "a", "--dd", "b",
168 },
169 };
170 var args = try clap.parseEx(clap.Help, params, &iter, .{ .allocator = testing.allocator });
171 defer args.deinit();
172
173 try testing.expect(args.flag("-a"));
174 try testing.expect(args.flag("--aa"));
175 try testing.expect(!args.flag("-b"));
176 try testing.expect(!args.flag("--bb"));
177 try testing.expectEqualStrings("0", args.option("-c").?);
178 try testing.expectEqualStrings("0", args.option("--cc").?);
179 try testing.expectEqual(@as(usize, 1), args.positionals().len);
180 try testing.expectEqualStrings("something", args.positionals()[0]);
181 try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, args.options("-d"));
182 try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, args.options("--dd"));
183}
184
185test "empty" {
186 var iter = clap.args.SliceIterator{ .args = &.{} };
187 var args = try clap.parseEx(u8, &.{}, &iter, .{ .allocator = testing.allocator });
188 defer args.deinit();
189}
190
191fn testErr(
192 comptime params: []const clap.Param(u8),
193 args_strings: []const []const u8,
194 expected: []const u8,
195) !void {
196 var diag = clap.Diagnostic{};
197 var iter = clap.args.SliceIterator{ .args = args_strings };
198 _ = clap.parseEx(u8, params, &iter, .{
199 .allocator = testing.allocator,
200 .diagnostic = &diag,
201 }) catch |err| {
202 var buf: [1024]u8 = undefined;
203 var fbs = io.fixedBufferStream(&buf);
204 diag.report(fbs.writer(), err) catch return error.TestFailed;
205 try testing.expectEqualStrings(expected, fbs.getWritten());
206 return;
207 };
208
209 try testing.expect(false);
210}
211
212test "errors" {
213 const params = [_]clap.Param(u8){
214 .{
215 .id = 0,
216 .names = .{ .short = 'a', .long = "aa" },
217 },
218 .{
219 .id = 1,
220 .names = .{ .short = 'c', .long = "cc" },
221 .takes_value = .one,
222 },
223 };
224
225 try testErr(&params, &.{"q"}, "Invalid argument 'q'\n");
226 try testErr(&params, &.{"-q"}, "Invalid argument '-q'\n");
227 try testErr(&params, &.{"--q"}, "Invalid argument '--q'\n");
228 try testErr(&params, &.{"--q=1"}, "Invalid argument '--q'\n");
229 try testErr(&params, &.{"-a=1"}, "The argument '-a' does not take a value\n");
230 try testErr(&params, &.{"--aa=1"}, "The argument '--aa' does not take a value\n");
231 try testErr(&params, &.{"-c"}, "The argument '-c' requires a value but none was supplied\n");
232 try testErr(
233 &params,
234 &.{"--cc"},
235 "The argument '--cc' requires a value but none was supplied\n",
236 );
237}
diff --git a/clap/parsers.zig b/clap/parsers.zig
new file mode 100644
index 0000000..49b95a9
--- /dev/null
+++ b/clap/parsers.zig
@@ -0,0 +1,48 @@
1const std = @import("std");
2
3const fmt = std.fmt;
4
5pub const default = .{
6 .string = string,
7 .str = string,
8 .u8 = int(u8, 0),
9 .u16 = int(u16, 0),
10 .u32 = int(u32, 0),
11 .u64 = int(u64, 0),
12 .usize = int(usize, 0),
13 .i8 = int(i8, 0),
14 .i16 = int(i16, 0),
15 .i32 = int(i32, 0),
16 .i64 = int(i64, 0),
17 .isize = int(isize, 0),
18 .f32 = float(f32),
19 .f64 = float(f64),
20};
21
22pub fn string(in: []const u8) error{}![]const u8 {
23 return in;
24}
25
26pub fn int(comptime T: type, comptime radix: u8) fn ([]const u8) fmt.ParseIntError!T {
27 return struct {
28 fn parse(in: []const u8) fmt.ParseIntError!T {
29 return fmt.parseInt(T, in, radix);
30 }
31 }.parse;
32}
33
34pub fn float(comptime T: type) fn ([]const u8) fmt.ParseFloatError!T {
35 return struct {
36 fn parse(in: []const u8) fmt.ParseFloatError!T {
37 return fmt.parseFloat(T, in);
38 }
39 }.parse;
40}
41
42fn ReturnType(comptime P: type) type {
43 return @typeInfo(P).Fn.return_type.?;
44}
45
46pub fn Result(comptime P: type) type {
47 return @typeInfo(ReturnType(P)).ErrorUnion.payload;
48}
diff --git a/clap/streaming.zig b/clap/streaming.zig
index 8eca51a..2ab9c8d 100644
--- a/clap/streaming.zig
+++ b/clap/streaming.zig
@@ -10,7 +10,7 @@ const mem = std.mem;
10const os = std.os; 10const os = std.os;
11const testing = std.testing; 11const testing = std.testing;
12 12
13/// The result returned from StreamingClap.next 13/// The result returned from Clap.next
14pub fn Arg(comptime Id: type) type { 14pub fn Arg(comptime Id: type) type {
15 return struct { 15 return struct {
16 const Self = @This(); 16 const Self = @This();
@@ -20,10 +20,18 @@ pub fn Arg(comptime Id: type) type {
20 }; 20 };
21} 21}
22 22
23pub const Error = error{
24 MissingValue,
25 InvalidArgument,
26 DoesntTakeValue,
27};
28
23/// A command line argument parser which, given an ArgIterator, will parse arguments according 29/// A command line argument parser which, given an ArgIterator, will parse arguments according
24/// to the params. StreamingClap parses in an iterating manner, so you have to use a loop 30/// to the params. Clap parses in an iterating manner, so you have to use a loop together with
25/// together with StreamingClap.next to parse all the arguments of your program. 31/// Clap.next to parse all the arguments of your program.
26pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { 32///
33/// This parser is the building block for all the more complicated parsers.
34pub fn Clap(comptime Id: type, comptime ArgIterator: type) type {
27 return struct { 35 return struct {
28 const State = union(enum) { 36 const State = union(enum) {
29 normal, 37 normal,
@@ -71,7 +79,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
71 continue; 79 continue;
72 if (param.takes_value == .none) { 80 if (param.takes_value == .none) {
73 if (maybe_value != null) 81 if (maybe_value != null)
74 return parser.err(arg, .{ .long = name }, error.DoesntTakeValue); 82 return parser.err(arg, .{ .long = name }, Error.DoesntTakeValue);
75 83
76 return Arg(Id){ .param = param }; 84 return Arg(Id){ .param = param };
77 } 85 }
@@ -81,13 +89,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
81 break :blk v; 89 break :blk v;
82 90
83 break :blk parser.iter.next() orelse 91 break :blk parser.iter.next() orelse
84 return parser.err(arg, .{ .long = name }, error.MissingValue); 92 return parser.err(arg, .{ .long = name }, Error.MissingValue);
85 }; 93 };
86 94
87 return Arg(Id){ .param = param, .value = value }; 95 return Arg(Id){ .param = param, .value = value };
88 } 96 }
89 97
90 return parser.err(arg, .{ .long = name }, error.InvalidArgument); 98 return parser.err(arg, .{ .long = name }, Error.InvalidArgument);
91 }, 99 },
92 .short => return try parser.chaining(.{ 100 .short => return try parser.chaining(.{
93 .arg = arg, 101 .arg = arg,
@@ -105,7 +113,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
105 113
106 return Arg(Id){ .param = param, .value = arg }; 114 return Arg(Id){ .param = param, .value = arg };
107 } else { 115 } else {
108 return parser.err(arg, .{}, error.InvalidArgument); 116 return parser.err(arg, .{}, Error.InvalidArgument);
109 }, 117 },
110 } 118 }
111 } 119 }
@@ -137,13 +145,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
137 const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false; 145 const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false;
138 if (param.takes_value == .none) { 146 if (param.takes_value == .none) {
139 if (next_is_eql) 147 if (next_is_eql)
140 return parser.err(arg, .{ .short = short }, error.DoesntTakeValue); 148 return parser.err(arg, .{ .short = short }, Error.DoesntTakeValue);
141 return Arg(Id){ .param = param }; 149 return Arg(Id){ .param = param };
142 } 150 }
143 151
144 if (arg.len <= next_index) { 152 if (arg.len <= next_index) {
145 const value = parser.iter.next() orelse 153 const value = parser.iter.next() orelse
146 return parser.err(arg, .{ .short = short }, error.MissingValue); 154 return parser.err(arg, .{ .short = short }, Error.MissingValue);
147 155
148 return Arg(Id){ .param = param, .value = value }; 156 return Arg(Id){ .param = param, .value = value };
149 } 157 }
@@ -154,7 +162,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
154 return Arg(Id){ .param = param, .value = arg[next_index..] }; 162 return Arg(Id){ .param = param, .value = arg[next_index..] };
155 } 163 }
156 164
157 return parser.err(arg, .{ .short = arg[index] }, error.InvalidArgument); 165 return parser.err(arg, .{ .short = arg[index] }, Error.InvalidArgument);
158 } 166 }
159 167
160 fn positionalParam(parser: *@This()) ?*const clap.Param(Id) { 168 fn positionalParam(parser: *@This()) ?*const clap.Param(Id) {
@@ -209,7 +217,7 @@ fn testNoErr(
209 results: []const Arg(u8), 217 results: []const Arg(u8),
210) !void { 218) !void {
211 var iter = args.SliceIterator{ .args = args_strings }; 219 var iter = args.SliceIterator{ .args = args_strings };
212 var c = StreamingClap(u8, args.SliceIterator){ 220 var c = Clap(u8, args.SliceIterator){
213 .params = params, 221 .params = params,
214 .iter = &iter, 222 .iter = &iter,
215 }; 223 };
@@ -236,7 +244,7 @@ fn testErr(
236) !void { 244) !void {
237 var diag: clap.Diagnostic = undefined; 245 var diag: clap.Diagnostic = undefined;
238 var iter = args.SliceIterator{ .args = args_strings }; 246 var iter = args.SliceIterator{ .args = args_strings };
239 var c = StreamingClap(u8, args.SliceIterator){ 247 var c = Clap(u8, args.SliceIterator){
240 .params = params, 248 .params = params,
241 .iter = &iter, 249 .iter = &iter,
242 .diagnostic = &diag, 250 .diagnostic = &diag,