summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2018-09-06 17:11:58 +0200
committerGravatar Jimmi Holst Christensen2018-09-06 17:11:58 +0200
commit5e1480a7a7537451f7196498ac2988bda8273a9b (patch)
tree2c85b66feeeeec1114d9dbe2608ef64f3ef5ee76 /src
parentUpdated to use pass-by-value where possible (diff)
downloadzig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.tar.gz
zig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.tar.xz
zig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.zip
Removed the extended api. Refactored tests
Diffstat (limited to 'src')
-rw-r--r--src/core.zig376
-rw-r--r--src/extended.zig233
2 files changed, 0 insertions, 609 deletions
diff --git a/src/core.zig b/src/core.zig
deleted file mode 100644
index bdd1bf4..0000000
--- a/src/core.zig
+++ /dev/null
@@ -1,376 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const os = std.os;
5const heap = std.heap;
6const mem = std.mem;
7const debug = std.debug;
8
9/// The names a ::Param can have.
10pub const Names = struct {
11 /// No prefix
12 bare: ?[]const u8,
13
14 /// '-' prefix
15 short: ?u8,
16
17 /// '--' prefix
18 long: ?[]const u8,
19
20 /// Initializes no names
21 pub fn none() Names {
22 return Names{
23 .bare = null,
24 .short = null,
25 .long = null,
26 };
27 }
28
29 /// Initializes a bare name
30 pub fn bare(b: []const u8) Names {
31 return Names{
32 .bare = b,
33 .short = null,
34 .long = null,
35 };
36 }
37
38 /// Initializes a short name
39 pub fn short(s: u8) Names {
40 return Names{
41 .bare = null,
42 .short = s,
43 .long = null,
44 };
45 }
46
47 /// Initializes a long name
48 pub fn long(l: []const u8) Names {
49 return Names{
50 .bare = null,
51 .short = null,
52 .long = l,
53 };
54 }
55
56 /// Initializes a name with a prefix.
57 /// ::short is set to ::name[0], and ::long is set to ::name.
58 /// This function asserts that ::name.len != 0
59 pub fn prefix(name: []const u8) Names {
60 debug.assert(name.len != 0);
61
62 return Names{
63 .bare = null,
64 .short = name[0],
65 .long = name,
66 };
67 }
68};
69
70/// Represents a parameter for the command line.
71/// Parameters come in three kinds:
72/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
73/// * They can take a value three different ways.
74/// * "-a value"
75/// * "-a=value"
76/// * "-avalue"
77/// * They chain if they don't take values: "-abc".
78/// * The last given parameter can take a value in the same way that a single parameter can:
79/// * "-abc value"
80/// * "-abc=value"
81/// * "-abcvalue"
82/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
83/// can describe the paramter.
84/// * They can take a value two different ways.
85/// * "--long-param value"
86/// * "--long-param=value"
87/// * Bare ("bare"): Should be used as for sub-commands and other keywords.
88/// * They can take a value two different ways.
89/// * "command value"
90/// * "command=value"
91/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or
92/// an expression to parse.
93/// * Value parameters must take a value.
94pub fn Param(comptime Id: type) type {
95 return struct {
96 const Self = this;
97
98 id: Id,
99 takes_value: bool,
100 names: Names,
101
102 pub fn init(id: Id, takes_value: bool, names: Names) Self {
103 // Assert, that if the param have no name, then it has to take
104 // a value.
105 debug.assert(names.bare != null or
106 names.long != null or
107 names.short != null or
108 takes_value);
109
110 return Self{
111 .id = id,
112 .takes_value = takes_value,
113 .names = names,
114 };
115 }
116 };
117}
118
119/// The result returned from ::Clap.next
120pub fn Arg(comptime Id: type) type {
121 return struct {
122 const Self = this;
123
124 param: *const Param(Id),
125 value: ?[]const u8,
126
127 pub fn init(param: *const Param(Id), value: ?[]const u8) Self {
128 return Self{
129 .param = param,
130 .value = value,
131 };
132 }
133 };
134}
135
136/// A interface for iterating over command line arguments
137pub fn ArgIterator(comptime E: type) type {
138 return struct {
139 const Self = this;
140 const Error = E;
141
142 nextFn: fn (iter: *Self) Error!?[]const u8,
143
144 pub fn next(iter: *Self) Error!?[]const u8 {
145 return iter.nextFn(iter);
146 }
147 };
148}
149
150/// An ::ArgIterator, which iterates over a slice of arguments.
151/// This implementation does not allocate.
152pub const ArgSliceIterator = struct {
153 const Error = error{};
154
155 args: []const []const u8,
156 index: usize,
157 iter: ArgIterator(Error),
158
159 pub fn init(args: []const []const u8) ArgSliceIterator {
160 return ArgSliceIterator{
161 .args = args,
162 .index = 0,
163 .iter = ArgIterator(Error){ .nextFn = nextFn },
164 };
165 }
166
167 fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 {
168 const self = @fieldParentPtr(ArgSliceIterator, "iter", iter);
169 if (self.args.len <= self.index)
170 return null;
171
172 defer self.index += 1;
173 return self.args[self.index];
174 }
175};
176
177/// An ::ArgIterator, which wraps the ArgIterator in ::std.
178/// On windows, this iterator allocates.
179pub const OsArgIterator = struct {
180 const Error = os.ArgIterator.NextError;
181
182 arena: heap.ArenaAllocator,
183 args: os.ArgIterator,
184 iter: ArgIterator(Error),
185
186 pub fn init(allocator: *mem.Allocator) OsArgIterator {
187 return OsArgIterator{
188 .arena = heap.ArenaAllocator.init(allocator),
189 .args = os.args(),
190 .iter = ArgIterator(Error){ .nextFn = nextFn },
191 };
192 }
193
194 pub fn deinit(iter: *OsArgIterator) void {
195 iter.arena.deinit();
196 }
197
198 fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 {
199 const self = @fieldParentPtr(OsArgIterator, "iter", iter);
200 if (builtin.os == builtin.Os.windows) {
201 return try self.args.next(self.allocator) orelse return null;
202 } else {
203 return self.args.nextPosix();
204 }
205 }
206};
207
208/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
209/// to the ::params. ::Clap parses in an iterating manner, so you have to use a loop together with
210/// ::Clap.next to parse all the arguments of your program.
211pub fn Clap(comptime Id: type, comptime ArgError: type) type {
212 return struct {
213 const Self = this;
214
215 const State = union(enum) {
216 Normal,
217 Chaining: Chaining,
218
219 const Chaining = struct {
220 arg: []const u8,
221 index: usize,
222 };
223 };
224
225 params: []const Param(Id),
226 iter: *ArgIterator(ArgError),
227 state: State,
228
229 pub fn init(params: []const Param(Id), iter: *ArgIterator(ArgError)) Self {
230 var res = Self{
231 .params = params,
232 .iter = iter,
233 .state = State.Normal,
234 };
235
236 return res;
237 }
238
239 /// Get the next ::Arg that matches a ::Param.
240 pub fn next(clap: *Self) !?Arg(Id) {
241 const ArgInfo = struct {
242 const Kind = enum {
243 Long,
244 Short,
245 Bare,
246 };
247
248 arg: []const u8,
249 kind: Kind,
250 };
251
252 switch (clap.state) {
253 State.Normal => {
254 const full_arg = (try clap.iter.next()) orelse return null;
255 const arg_info = blk: {
256 var arg = full_arg;
257 var kind = ArgInfo.Kind.Bare;
258
259 if (mem.startsWith(u8, arg, "--")) {
260 arg = arg[2..];
261 kind = ArgInfo.Kind.Long;
262 } else if (mem.startsWith(u8, arg, "-")) {
263 arg = arg[1..];
264 kind = ArgInfo.Kind.Short;
265 }
266
267 // We allow long arguments to go without a name.
268 // This allows the user to use "--" for something important
269 if (kind != ArgInfo.Kind.Long and arg.len == 0)
270 return error.InvalidArgument;
271
272 break :blk ArgInfo{ .arg = arg, .kind = kind };
273 };
274
275 const arg = arg_info.arg;
276 const kind = arg_info.kind;
277 const eql_index = mem.indexOfScalar(u8, arg, '=');
278
279 switch (kind) {
280 ArgInfo.Kind.Bare, ArgInfo.Kind.Long => {
281 for (clap.params) |*param| {
282 const match = switch (kind) {
283 ArgInfo.Kind.Bare => param.names.bare orelse continue,
284 ArgInfo.Kind.Long => param.names.long orelse continue,
285 else => unreachable,
286 };
287 const name = if (eql_index) |i| arg[0..i] else arg;
288 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
289
290 if (!mem.eql(u8, name, match))
291 continue;
292 if (!param.takes_value) {
293 if (maybe_value != null)
294 return error.DoesntTakeValue;
295
296 return Arg(Id).init(param, null);
297 }
298
299 const value = blk: {
300 if (maybe_value) |v|
301 break :blk v;
302
303 break :blk (try clap.iter.next()) orelse return error.MissingValue;
304 };
305
306 return Arg(Id).init(param, value);
307 }
308 },
309 ArgInfo.Kind.Short => {
310 return try clap.chainging(State.Chaining{
311 .arg = full_arg,
312 .index = (full_arg.len - arg.len),
313 });
314 },
315 }
316
317 // We do a final pass to look for value parameters matches
318 if (kind == ArgInfo.Kind.Bare) {
319 for (clap.params) |*param| {
320 if (param.names.bare) |_| continue;
321 if (param.names.short) |_| continue;
322 if (param.names.long) |_| continue;
323
324 return Arg(Id).init(param, arg);
325 }
326 }
327
328 return error.InvalidArgument;
329 },
330 @TagType(State).Chaining => |state| return try clap.chainging(state),
331 }
332 }
333
334 fn chainging(clap: *Self, state: State.Chaining) !?Arg(Id) {
335 const arg = state.arg;
336 const index = state.index;
337 const next_index = index + 1;
338
339 for (clap.params) |*param| {
340 const short = param.names.short orelse continue;
341 if (short != arg[index])
342 continue;
343
344 // Before we return, we have to set the new state of the clap
345 defer {
346 if (arg.len <= next_index or param.takes_value) {
347 clap.state = State.Normal;
348 } else {
349 clap.state = State{
350 .Chaining = State.Chaining{
351 .arg = arg,
352 .index = next_index,
353 },
354 };
355 }
356 }
357
358 if (!param.takes_value)
359 return Arg(Id).init(param, null);
360
361 if (arg.len <= next_index) {
362 const value = (try clap.iter.next()) orelse return error.MissingValue;
363 return Arg(Id).init(param, value);
364 }
365
366 if (arg[next_index] == '=') {
367 return Arg(Id).init(param, arg[next_index + 1 ..]);
368 }
369
370 return Arg(Id).init(param, arg[next_index..]);
371 }
372
373 return error.InvalidArgument;
374 }
375 };
376}
diff --git a/src/extended.zig b/src/extended.zig
deleted file mode 100644
index f7fc87d..0000000
--- a/src/extended.zig
+++ /dev/null
@@ -1,233 +0,0 @@
1pub const core = @import("core.zig");
2
3const builtin = @import("builtin");
4const std = @import("std");
5
6const mem = std.mem;
7const fmt = std.fmt;
8const debug = std.debug;
9const io = std.io;
10
11const assert = debug.assert;
12
13pub const Param = struct {
14 field: []const u8,
15 names: core.Names,
16 kind: Kind,
17
18 required: bool,
19 position: ?usize,
20
21 pub fn flag(field: []const u8, names: core.Names) Param {
22 return init(
23 field,
24 names,
25 Kind.Flag,
26 );
27 }
28
29 pub fn option(
30 field: []const u8,
31 names: core.Names,
32 comptime parser: Parser,
33 ) Param {
34 return init(
35 field,
36 names,
37 Kind{ .Option = parser },
38 );
39 }
40
41 pub fn subcommand(
42 field: []const u8,
43 names: core.Names,
44 comptime command: Command,
45 ) Param {
46 return init(
47 field,
48 names,
49 Kind{ .Subcommand = command },
50 );
51 }
52
53 pub fn init(field: []const u8, names: core.Names, kind: Kind) Param {
54 return Param{
55 .field = field,
56 .names = names,
57 .kind = kind,
58 .required = false,
59 .position = null,
60 };
61 }
62
63 pub const Kind = union(enum) {
64 Flag,
65 Option: Parser,
66 Subcommand: Command,
67 };
68};
69
70const Opaque = @OpaqueType();
71pub const Command = struct {
72 params: []const Param,
73
74 Result: type,
75 default: *const Opaque,
76
77 pub fn init(comptime Result: type, default: *const Result, params: []const Param) Command {
78 return Command{
79 .params = params,
80 .Result = Result,
81 .default = @ptrCast(*const Opaque, default),
82 };
83 }
84};
85
86pub const Parser = struct {
87 const UnsafeFunction = *const void;
88
89 FieldType: type,
90 Errors: type,
91 func: UnsafeFunction,
92
93 pub fn init(comptime FieldType: type, comptime Errors: type, func: ParseFunc(FieldType, Errors)) Parser {
94 return Parser{
95 .FieldType = FieldType,
96 .Errors = Errors,
97 .func = @ptrCast(UnsafeFunction, func),
98 };
99 }
100
101 fn parse(comptime parser: Parser, field_ptr: *parser.FieldType, arg: []const u8) parser.Errors!void {
102 return @ptrCast(ParseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
103 }
104
105 fn ParseFunc(comptime FieldType: type, comptime Errors: type) type {
106 return fn (*FieldType, []const u8) Errors!void;
107 }
108
109 pub fn int(comptime Int: type, comptime radix: u8) Parser {
110 const func = struct {
111 fn i(field_ptr: *Int, arg: []const u8) !void {
112 field_ptr.* = try fmt.parseInt(Int, arg, radix);
113 }
114 }.i;
115 return Parser.init(Int, @typeOf(func).ReturnType.ErrorSet, func);
116 }
117
118 const string = Parser.init([]const u8, error{}, struct {
119 fn s(field_ptr: *[]const u8, arg: []const u8) (error{}!void) {
120 field_ptr.* = arg;
121 }
122 }.s);
123};
124
125pub fn Clap(comptime Result: type) type {
126 return struct {
127 const Self = this;
128
129 default: Result,
130 params: []const Param,
131
132 // TODO: pass-by-value
133 pub fn parse(
134 comptime clap: *const Self,
135 comptime Error: type,
136 iter: *core.ArgIterator(Error),
137 ) !Result {
138 // We initialize the core.Clap without any params, and fill them out in parseHelper.
139 var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter);
140
141 const top_level_command = comptime Command.init(Result, &clap.default, clap.params);
142 return try parseHelper(&top_level_command, Error, &c);
143 }
144
145 // TODO: pass-by-value
146 fn parseHelper(
147 comptime command: *const Command,
148 comptime Error: type,
149 clap: *core.Clap(usize, Error),
150 ) !command.Result {
151 var result = @ptrCast(*const command.Result, command.default).*;
152
153 var handled = comptime blk: {
154 var res: [command.params.len]bool = undefined;
155 for (command.params) |p, i| {
156 res[i] = !p.required;
157 }
158
159 break :blk res;
160 };
161
162 // We replace the current clap with the commands parameters, so that we preserve the that
163 // claps state. This is important, as core.Clap could be in a Chaining state, and
164 // constructing a new core.Clap would skip the last chaining arguments.
165 clap.params = comptime blk: {
166 var res: [command.params.len]core.Param(usize) = undefined;
167
168 for (command.params) |p, i| {
169 const id = i;
170 res[id] = core.Param(usize){
171 .id = id,
172 .takes_value = p.kind == Param.Kind.Option,
173 .names = p.names,
174 };
175 }
176
177 break :blk res;
178 };
179
180 var pos: usize = 0;
181
182 arg_loop: while (try clap.next()) |arg| : (pos += 1) {
183 inline for (command.params) |param, i| {
184 if (arg.param.id == i and (param.position orelse pos) == pos) {
185 handled[i] = true;
186
187 switch (param.kind) {
188 Param.Kind.Flag => {
189 getFieldPtr(&result, param.field).* = true;
190 },
191 Param.Kind.Option => |parser| {
192 try parser.parse(getFieldPtr(&result, param.field), arg.value.?);
193 },
194 Param.Kind.Subcommand => |sub_command| {
195 getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap);
196
197 // After parsing a subcommand, there should be no arguments left.
198 break :arg_loop;
199 },
200 }
201 continue :arg_loop;
202 }
203 }
204
205 return error.InvalidArgument;
206 }
207
208 for (handled) |h| {
209 if (!h)
210 return error.ParamNotHandled;
211 }
212
213 return result;
214 }
215
216 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
217 var inst: Struct = undefined;
218 const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse {
219 return @typeOf(&@field(inst, field));
220 };
221
222 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1 ..]);
223 }
224
225 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
226 const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse {
227 return &@field(curr, field);
228 };
229
230 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1 ..]);
231 }
232 };
233}