summaryrefslogtreecommitdiff
path: root/clap/untyped.zig
diff options
context:
space:
mode:
Diffstat (limited to 'clap/untyped.zig')
-rw-r--r--clap/untyped.zig394
1 files changed, 0 insertions, 394 deletions
diff --git a/clap/untyped.zig b/clap/untyped.zig
deleted file mode 100644
index c9b6621..0000000
--- a/clap/untyped.zig
+++ /dev/null
@@ -1,394 +0,0 @@
1const clap = @import("../clap.zig");
2const std = @import("std");
3
4const builtin = std.builtin;
5const debug = std.debug;
6const heap = std.heap;
7const io = std.io;
8const mem = std.mem;
9const process = std.process;
10const testing = std.testing;
11
12/// Same as `parseEx` but uses the `args.OsIterator` by default.
13pub fn parse(
14 comptime Id: type,
15 comptime params: []const clap.Param(Id),
16 opt: clap.ParseOptions,
17) !Result(Arguments(Id, params, []const []const u8, &[_][]const u8{})) {
18 var arena = heap.ArenaAllocator.init(opt.allocator);
19 errdefer arena.deinit();
20
21 var iter = try process.ArgIterator.initWithAllocator(arena.allocator());
22 const exe_arg = iter.next();
23
24 const result = try parseEx(Id, params, &iter, .{
25 // Let's reuse the arena from the `OSIterator` since we already have it.
26 .allocator = arena.allocator(),
27 .diagnostic = opt.diagnostic,
28 });
29
30 return Result(Arguments(Id, params, []const []const u8, &.{})){
31 .args = result.args,
32 .positionals = result.positionals,
33 .exe_arg = exe_arg,
34 .arena = arena,
35 };
36}
37
38pub fn Result(comptime Args: type) type {
39 return struct {
40 args: Args,
41 positionals: []const []const u8,
42 exe_arg: ?[]const u8,
43 arena: std.heap.ArenaAllocator,
44
45 pub fn deinit(result: @This()) void {
46 result.arena.deinit();
47 }
48 };
49}
50
51/// Parses the command line arguments passed into the program based on an
52/// array of `Param`s.
53pub fn parseEx(
54 comptime Id: type,
55 comptime params: []const clap.Param(Id),
56 iter: anytype,
57 opt: clap.ParseOptions,
58) !ResultEx(Arguments(Id, params, []const []const u8, &.{})) {
59 const allocator = opt.allocator;
60 var positionals = std.ArrayList([]const u8).init(allocator);
61 var args = Arguments(Id, params, std.ArrayListUnmanaged([]const u8), .{}){};
62 errdefer deinitArgs(allocator, &args);
63
64 var stream = clap.streaming.Clap(Id, @typeInfo(@TypeOf(iter)).Pointer.child){
65 .params = params,
66 .iter = iter,
67 .diagnostic = opt.diagnostic,
68 };
69 while (try stream.next()) |arg| {
70 inline for (params) |*param| {
71 if (param == arg.param) {
72 const longest = comptime param.names.longest();
73 switch (longest.kind) {
74 .short, .long => switch (param.takes_value) {
75 .none => @field(args, longest.name) = true,
76 .one => @field(args, longest.name) = arg.value.?,
77 .many => try @field(args, longest.name).append(allocator, arg.value.?),
78 },
79 .positinal => try positionals.append(arg.value.?),
80 }
81 }
82 }
83 }
84
85 var result_args = Arguments(Id, params, []const []const u8, &.{}){};
86 inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {
87 if (field.field_type == std.ArrayListUnmanaged([]const u8)) {
88 const slice = @field(args, field.name).toOwnedSlice(allocator);
89 @field(result_args, field.name) = slice;
90 } else {
91 @field(result_args, field.name) = @field(args, field.name);
92 }
93 }
94
95 return ResultEx(@TypeOf(result_args)){
96 .args = result_args,
97 .positionals = positionals.toOwnedSlice(),
98 .allocator = allocator,
99 };
100}
101
102pub fn ResultEx(comptime Args: type) type {
103 return struct {
104 args: Args,
105 positionals: []const []const u8,
106 allocator: mem.Allocator,
107
108 pub fn deinit(result: *@This()) void {
109 deinitArgs(result.allocator, &result.args);
110 result.allocator.free(result.positionals);
111 }
112 };
113}
114
115fn deinitArgs(allocator: mem.Allocator, args: anytype) void {
116 const Args = @TypeOf(args.*);
117 inline for (@typeInfo(Args).Struct.fields) |field| {
118 if (field.field_type == []const []const u8)
119 allocator.free(@field(args, field.name));
120 if (field.field_type == std.ArrayListUnmanaged([]const u8))
121 @field(args, field.name).deinit(allocator);
122 }
123}
124
125fn Arguments(
126 comptime Id: type,
127 comptime params: []const clap.Param(Id),
128 comptime MultiArgsType: type,
129 comptime multi_args_default: MultiArgsType,
130) type {
131 var fields: [params.len]builtin.TypeInfo.StructField = undefined;
132
133 var i: usize = 0;
134 for (params) |param| {
135 const longest = param.names.longest();
136 if (longest.kind == .positinal)
137 continue;
138
139 const field_type = switch (param.takes_value) {
140 .none => bool,
141 .one => ?[]const u8,
142 .many => MultiArgsType,
143 };
144 fields[i] = .{
145 .name = longest.name,
146 .field_type = field_type,
147 .default_value = switch (param.takes_value) {
148 .none => &false,
149 .one => &@as(?[]const u8, null),
150 .many => &multi_args_default,
151 },
152 .is_comptime = false,
153 .alignment = @alignOf(field_type),
154 };
155 i += 1;
156 }
157
158 return @Type(.{ .Struct = .{
159 .layout = .Auto,
160 .fields = fields[0..i],
161 .decls = &.{},
162 .is_tuple = false,
163 } });
164}
165
166test "" {
167 const params = comptime &.{
168 parseParam("-a, --aa") catch unreachable,
169 parseParam("-b, --bb") catch unreachable,
170 parseParam("-c, --cc <V>") catch unreachable,
171 parseParam("-d, --dd <V>...") catch unreachable,
172 parseParam("<P>") catch unreachable,
173 };
174
175 var iter = clap.args.SliceIterator{
176 .args = &.{
177 "-a", "-c", "0", "something", "-d", "a", "--dd", "b",
178 },
179 };
180 var res = try clap.untyped.parseEx(clap.Help, params, &iter, .{
181 .allocator = testing.allocator,
182 });
183 defer res.deinit();
184
185 try testing.expect(res.args.aa);
186 try testing.expect(!res.args.bb);
187 try testing.expectEqualStrings("0", res.args.cc.?);
188 try testing.expectEqual(@as(usize, 1), res.positionals.len);
189 try testing.expectEqualStrings("something", res.positionals[0]);
190 try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, res.args.dd);
191}
192
193test "empty" {
194 var iter = clap.args.SliceIterator{ .args = &.{} };
195 var res = try clap.untyped.parseEx(u8, &.{}, &iter, .{ .allocator = testing.allocator });
196 defer res.deinit();
197}
198
199fn testErr(
200 comptime params: []const clap.Param(u8),
201 args_strings: []const []const u8,
202 expected: []const u8,
203) !void {
204 var diag = clap.Diagnostic{};
205 var iter = clap.args.SliceIterator{ .args = args_strings };
206 _ = clap.untyped.parseEx(u8, params, &iter, .{
207 .allocator = testing.allocator,
208 .diagnostic = &diag,
209 }) catch |err| {
210 var buf: [1024]u8 = undefined;
211 var fbs = io.fixedBufferStream(&buf);
212 diag.report(fbs.writer(), err) catch return error.TestFailed;
213 try testing.expectEqualStrings(expected, fbs.getWritten());
214 return;
215 };
216
217 try testing.expect(false);
218}
219
220test "errors" {
221 const params = [_]clap.Param(u8){
222 .{
223 .id = 0,
224 .names = .{ .short = 'a', .long = "aa" },
225 },
226 .{
227 .id = 1,
228 .names = .{ .short = 'c', .long = "cc" },
229 .takes_value = .one,
230 },
231 };
232
233 try testErr(&params, &.{"q"}, "Invalid argument 'q'\n");
234 try testErr(&params, &.{"-q"}, "Invalid argument '-q'\n");
235 try testErr(&params, &.{"--q"}, "Invalid argument '--q'\n");
236 try testErr(&params, &.{"--q=1"}, "Invalid argument '--q'\n");
237 try testErr(&params, &.{"-a=1"}, "The argument '-a' does not take a value\n");
238 try testErr(&params, &.{"--aa=1"}, "The argument '--aa' does not take a value\n");
239 try testErr(&params, &.{"-c"}, "The argument '-c' requires a value but none was supplied\n");
240 try testErr(
241 &params,
242 &.{"--cc"},
243 "The argument '--cc' requires a value but none was supplied\n",
244 );
245}
246
247/// Takes a string and parses it to a Param(clap.Help).
248/// This is the reverse of 'help' but for at single parameter only.
249pub fn parseParam(line: []const u8) !clap.Param(clap.Help) {
250 // This function become a lot less ergonomic to use once you hit the eval branch quota. To
251 // avoid this we pick a sane default. Sadly, the only sane default is the biggest possible
252 // value. If we pick something a lot smaller and a user hits the quota after that, they have
253 // no way of overriding it, since we set it here.
254 // We can recosider this again if:
255 // * We get parseParams: https://github.com/Hejsil/zig-clap/issues/39
256 // * We get a larger default branch quota in the zig compiler (stage 2).
257 // * Someone points out how this is a really bad idea.
258 @setEvalBranchQuota(std.math.maxInt(u32));
259
260 var found_comma = false;
261 var it = mem.tokenize(u8, line, " \t");
262 var param_str = it.next() orelse return error.NoParamFound;
263
264 const short_name = if (!mem.startsWith(u8, param_str, "--") and
265 mem.startsWith(u8, param_str, "-"))
266 blk: {
267 found_comma = param_str[param_str.len - 1] == ',';
268 if (found_comma)
269 param_str = param_str[0 .. param_str.len - 1];
270
271 if (param_str.len != 2)
272 return error.InvalidShortParam;
273
274 const short_name = param_str[1];
275 if (!found_comma) {
276 var res = parseParamRest(it.rest());
277 res.names.short = short_name;
278 return res;
279 }
280
281 param_str = it.next() orelse return error.NoParamFound;
282 break :blk short_name;
283 } else null;
284
285 const long_name = if (mem.startsWith(u8, param_str, "--")) blk: {
286 if (param_str[param_str.len - 1] == ',')
287 return error.TrailingComma;
288
289 break :blk param_str[2..];
290 } else if (found_comma) {
291 return error.TrailingComma;
292 } else if (short_name == null) {
293 return parseParamRest(mem.trimLeft(u8, line, " \t"));
294 } else null;
295
296 var res = parseParamRest(it.rest());
297 res.names.long = long_name;
298 res.names.short = short_name;
299 return res;
300}
301
302fn parseParamRest(line: []const u8) clap.Param(clap.Help) {
303 if (mem.startsWith(u8, line, "<")) blk: {
304 const len = mem.indexOfScalar(u8, line, '>') orelse break :blk;
305 const takes_many = mem.startsWith(u8, line[len + 1 ..], "...");
306 const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many);
307 return .{
308 .takes_value = if (takes_many) .many else .one,
309 .id = .{
310 .msg = mem.trim(u8, line[help_start..], " \t"),
311 .value = line[1..len],
312 },
313 };
314 }
315
316 return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } };
317}
318
319fn expectParam(expect: clap.Param(clap.Help), actual: clap.Param(clap.Help)) !void {
320 try testing.expectEqualStrings(expect.id.msg, actual.id.msg);
321 try testing.expectEqualStrings(expect.id.value, actual.id.value);
322 try testing.expectEqual(expect.names.short, actual.names.short);
323 try testing.expectEqual(expect.takes_value, actual.takes_value);
324 if (expect.names.long) |long| {
325 try testing.expectEqualStrings(long, actual.names.long.?);
326 } else {
327 try testing.expectEqual(@as(?[]const u8, null), actual.names.long);
328 }
329}
330
331test "parseParam" {
332 try expectParam(clap.Param(clap.Help){
333 .id = .{ .msg = "Help text", .value = "value" },
334 .names = .{ .short = 's', .long = "long" },
335 .takes_value = .one,
336 }, try parseParam("-s, --long <value> Help text"));
337
338 try expectParam(clap.Param(clap.Help){
339 .id = .{ .msg = "Help text", .value = "value" },
340 .names = .{ .short = 's', .long = "long" },
341 .takes_value = .many,
342 }, try parseParam("-s, --long <value>... Help text"));
343
344 try expectParam(clap.Param(clap.Help){
345 .id = .{ .msg = "Help text", .value = "value" },
346 .names = .{ .long = "long" },
347 .takes_value = .one,
348 }, try parseParam("--long <value> Help text"));
349
350 try expectParam(clap.Param(clap.Help){
351 .id = .{ .msg = "Help text", .value = "value" },
352 .names = .{ .short = 's' },
353 .takes_value = .one,
354 }, try parseParam("-s <value> Help text"));
355
356 try expectParam(clap.Param(clap.Help){
357 .id = .{ .msg = "Help text" },
358 .names = .{ .short = 's', .long = "long" },
359 }, try parseParam("-s, --long Help text"));
360
361 try expectParam(clap.Param(clap.Help){
362 .id = .{ .msg = "Help text" },
363 .names = .{ .short = 's' },
364 }, try parseParam("-s Help text"));
365
366 try expectParam(clap.Param(clap.Help){
367 .id = .{ .msg = "Help text" },
368 .names = .{ .long = "long" },
369 }, try parseParam("--long Help text"));
370
371 try expectParam(clap.Param(clap.Help){
372 .id = .{ .msg = "Help text", .value = "A | B" },
373 .names = .{ .long = "long" },
374 .takes_value = .one,
375 }, try parseParam("--long <A | B> Help text"));
376
377 try expectParam(clap.Param(clap.Help){
378 .id = .{ .msg = "Help text", .value = "A" },
379 .names = .{},
380 .takes_value = .one,
381 }, try parseParam("<A> Help text"));
382
383 try expectParam(clap.Param(clap.Help){
384 .id = .{ .msg = "Help text", .value = "A" },
385 .names = .{},
386 .takes_value = .many,
387 }, try parseParam("<A>... Help text"));
388
389 try testing.expectError(error.TrailingComma, parseParam("--long, Help"));
390 try testing.expectError(error.TrailingComma, parseParam("-s, Help"));
391 try testing.expectError(error.InvalidShortParam, parseParam("-ss Help"));
392 try testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help"));
393 try testing.expectError(error.InvalidShortParam, parseParam("- Help"));
394}