summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core.zig372
-rw-r--r--src/extended.zig264
2 files changed, 636 insertions, 0 deletions
diff --git a/src/core.zig b/src/core.zig
new file mode 100644
index 0000000..a3fb44c
--- /dev/null
+++ b/src/core.zig
@@ -0,0 +1,372 @@
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: &const Names) Self {
103 // Assert, that if the param have no name, then it has to take
104 // a value.
105 debug.assert(
106 names.bare != null or
107 names.long != null or
108 names.short != null or
109 takes_value
110 );
111
112 return Self{
113 .id = id,
114 .takes_value = takes_value,
115 .names = names.*,
116 };
117 }
118 };
119}
120
121/// The result returned from ::Clap.next
122pub fn Arg(comptime Id: type) type {
123 return struct {
124 const Self = this;
125
126 id: Id,
127 value: ?[]const u8,
128
129 pub fn init(id: Id, value: ?[]const u8) Self {
130 return Self {
131 .id = id,
132 .value = value,
133 };
134 }
135 };
136}
137
138/// A interface for iterating over command line arguments
139pub const ArgIterator = struct {
140 const Error = error{OutOfMemory};
141
142 nextFn: fn(iter: &ArgIterator, allocator: &mem.Allocator) Error!?[]const u8,
143
144 pub fn next(iter: &ArgIterator, allocator: &mem.Allocator) Error!?[]const u8 {
145 return iter.nextFn(iter, allocator);
146 }
147};
148
149/// An ::ArgIterator, which iterates over a slice of arguments.
150/// This implementation does not allocate.
151pub const ArgSliceIterator = struct {
152 args: []const []const u8,
153 index: usize,
154 iter: ArgIterator,
155
156 pub fn init(args: []const []const u8) ArgSliceIterator {
157 return ArgSliceIterator {
158 .args = args,
159 .index = 0,
160 .iter = ArgIterator {
161 .nextFn = nextFn,
162 },
163 };
164 }
165
166 fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 {
167 const self = @fieldParentPtr(ArgSliceIterator, "iter", iter);
168 if (self.args.len <= self.index)
169 return null;
170
171 defer self.index += 1;
172 return self.args[self.index];
173 }
174};
175
176/// An ::ArgIterator, which wraps the ArgIterator in ::std.
177/// On windows, this iterator allocates.
178pub const OsArgIterator = struct {
179 args: os.ArgIterator,
180 iter: ArgIterator,
181
182 pub fn init() OsArgIterator {
183 return OsArgIterator {
184 .args = os.args(),
185 .iter = ArgIterator {
186 .nextFn = nextFn,
187 },
188 };
189 }
190
191 fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 {
192 const self = @fieldParentPtr(OsArgIterator, "iter", iter);
193 if (builtin.os == builtin.Os.windows) {
194 return try self.args.next(allocator) ?? return null;
195 } else {
196 return self.args.nextPosix();
197 }
198 }
199};
200
201/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
202/// to the ::params. ::Clap parses in an iterating manner, so you have to use a loop together with
203/// ::Clap.next to parse all the arguments of your program.
204pub fn Clap(comptime Id: type) type {
205 return struct {
206 const Self = this;
207
208 const State = union(enum) {
209 Normal,
210 Chaining: Chaining,
211
212 const Chaining = struct {
213 arg: []const u8,
214 index: usize,
215 };
216 };
217
218 arena: heap.ArenaAllocator,
219 params: []const Param(Id),
220 inner: &ArgIterator,
221 state: State,
222
223 pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) Self {
224 var res = Self {
225 .arena = heap.ArenaAllocator.init(allocator),
226 .params = params,
227 .inner = inner,
228 .state = State.Normal,
229 };
230
231 return res;
232 }
233
234 pub fn deinit(iter: &Self) void {
235 iter.arena.deinit();
236 }
237
238 /// Get the next ::Arg that matches a ::Param.
239 pub fn next(iter: &Self) !?Arg(Id) {
240 const ArgInfo = struct {
241 const Kind = enum { Long, Short, Bare };
242
243 arg: []const u8,
244 kind: Kind,
245 };
246
247 switch (iter.state) {
248 State.Normal => {
249 const full_arg = (try iter.innerNext()) ?? return null;
250 const arg_info = blk: {
251 var arg = full_arg;
252 var kind = ArgInfo.Kind.Bare;
253
254 if (mem.startsWith(u8, arg, "--")) {
255 arg = arg[2..];
256 kind = ArgInfo.Kind.Long;
257 } else if (mem.startsWith(u8, arg, "-")) {
258 arg = arg[1..];
259 kind = ArgInfo.Kind.Short;
260 }
261
262 if (arg.len == 0)
263 return error.ArgWithNoName;
264
265 break :blk ArgInfo { .arg = arg, .kind = kind };
266 };
267
268 const arg = arg_info.arg;
269 const kind = arg_info.kind;
270 const eql_index = mem.indexOfScalar(u8, arg, '=');
271
272 switch (kind) {
273 ArgInfo.Kind.Bare,
274 ArgInfo.Kind.Long => {
275 for (iter.params) |*param| {
276 const match = switch (kind) {
277 ArgInfo.Kind.Bare => param.names.bare ?? continue,
278 ArgInfo.Kind.Long => param.names.long ?? continue,
279 else => unreachable,
280 };
281 const name = if (eql_index) |i| arg[0..i] else arg;
282 const maybe_value = if (eql_index) |i| arg[i + 1..] else null;
283
284 if (!mem.eql(u8, name, match))
285 continue;
286 if (!param.takes_value) {
287 if (maybe_value != null)
288 return error.DoesntTakeValue;
289
290 return Arg(Id).init(param.id, null);
291 }
292
293 const value = blk: {
294 if (maybe_value) |v|
295 break :blk v;
296
297 break :blk (try iter.innerNext()) ?? return error.MissingValue;
298 };
299
300 return Arg(Id).init(param.id, value);
301 }
302 },
303 ArgInfo.Kind.Short => {
304 return try iter.chainging(State.Chaining {
305 .arg = full_arg,
306 .index = (full_arg.len - arg.len),
307 });
308 },
309 }
310
311 // We do a final pass to look for value parameters matches
312 if (kind == ArgInfo.Kind.Bare) {
313 for (iter.params) |*param| {
314 if (param.names.bare) |_| continue;
315 if (param.names.short) |_| continue;
316 if (param.names.long) |_| continue;
317
318 return Arg(Id).init(param.id, arg);
319 }
320 }
321
322 return error.InvalidArgument;
323 },
324 @TagType(State).Chaining => |state| return try iter.chainging(state),
325 }
326 }
327
328 fn chainging(iter: &Self, state: &const State.Chaining) !?Arg(Id) {
329 const arg = state.arg;
330 const index = state.index;
331 const next_index = index + 1;
332
333 for (iter.params) |param| {
334 const short = param.names.short ?? continue;
335 if (short != arg[index])
336 continue;
337
338 // Before we return, we have to set the new state of the iterator
339 defer {
340 if (arg.len <= next_index or param.takes_value) {
341 iter.state = State.Normal;
342 } else {
343 iter.state = State { .Chaining = State.Chaining {
344 .arg = arg,
345 .index = next_index,
346 }};
347 }
348 }
349
350 if (!param.takes_value)
351 return Arg(Id).init(param.id, null);
352
353 if (arg.len <= next_index) {
354 const value = (try iter.innerNext()) ?? return error.MissingValue;
355 return Arg(Id).init(param.id, value);
356 }
357
358 if (arg[next_index] == '=') {
359 return Arg(Id).init(param.id, arg[next_index + 1..]);
360 }
361
362 return Arg(Id).init(param.id, arg[next_index..]);
363 }
364
365 return error.InvalidArgument;
366 }
367
368 fn innerNext(iter: &Self) !?[]const u8 {
369 return try iter.inner.next(&iter.arena.allocator);
370 }
371 };
372}
diff --git a/src/extended.zig b/src/extended.zig
new file mode 100644
index 0000000..9427b83
--- /dev/null
+++ b/src/extended.zig
@@ -0,0 +1,264 @@
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
13const Opaque = @OpaqueType();
14
15pub const Param = struct {
16 field: []const u8,
17 short: ?u8,
18 long: ?[]const u8,
19 takes_value: ?Parser,
20 required: bool,
21 position: ?usize,
22
23 pub fn short(s: u8) Param {
24 return Param{
25 .field = []u8{s},
26 .short = s,
27 .long = null,
28 .takes_value = null,
29 .required = false,
30 .position = null,
31 };
32 }
33
34 pub fn long(l: []const u8) Param {
35 return Param{
36 .field = l,
37 .short = null,
38 .long = l,
39 .takes_value = null,
40 .required = false,
41 .position = null,
42 };
43 }
44
45 pub fn value(f: []const u8) Param {
46 return Param{
47 .field = f,
48 .short = null,
49 .long = null,
50 .takes_value = null,
51 .required = false,
52 .position = null,
53 };
54 }
55
56 /// Initialize a ::Param.
57 /// If ::name.len == 0, then it's a value parameter: "value".
58 /// If ::name.len == 1, then it's a short parameter: "-s".
59 /// If ::name.len > 1, then it's a long parameter: "--long".
60 pub fn smart(name: []const u8) Param {
61 return Param{
62 .field = name,
63 .short = if (name.len == 1) name[0] else null,
64 .long = if (name.len > 1) name else null,
65 .takes_value = null,
66 .required = false,
67 .position = null,
68 };
69 }
70
71 pub fn with(param: &const Param, comptime field_name: []const u8, v: var) Param {
72 var res = param.*;
73 @field(res, field_name) = v;
74 return res;
75 }
76};
77
78pub const Command = struct {
79 field: []const u8,
80 name: []const u8,
81 params: []const Param,
82 sub_commands: []const Command,
83
84 Result: type,
85 defaults: &const Opaque,
86 parent: ?&const Command,
87
88 pub fn init(name: []const u8, comptime Result: type, defaults: &const Result, params: []const Param, sub_commands: []const Command) Command {
89 return Command{
90 .field = name,
91 .name = name,
92 .params = params,
93 .sub_commands = sub_commands,
94 .Result = Result,
95 .defaults = @ptrCast(&const Opaque, defaults),
96 .parent = null,
97 };
98 }
99
100 pub fn with(command: &const Command, comptime field_name: []const u8, v: var) Param {
101 var res = command.*;
102 @field(res, field_name) = v;
103 return res;
104 }
105
106 pub fn parse(comptime command: &const Command, allocator: &mem.Allocator, arg_iter: &core.ArgIterator) !command.Result {
107 const Parent = struct {};
108 var parent = Parent{};
109 return command.parseHelper(&parent, allocator, arg_iter);
110 }
111
112 fn parseHelper(comptime command: &const Command, parent: var, allocator: &mem.Allocator, arg_iter: &core.ArgIterator) !command.Result {
113 const Result = struct {
114 parent: @typeOf(parent),
115 result: command.Result,
116 };
117
118 var result = Result{
119 .parent = parent,
120 .result = @ptrCast(&const command.Result, command.defaults).*,
121 };
122
123 // In order for us to wrap the core api, we have to translate clap.Param into core.Param.
124 const core_params = comptime blk: {
125 var res: [command.params.len + command.sub_commands.len]core.Param(usize) = undefined;
126
127 for (command.params) |p, i| {
128 const id = i;
129 res[id] = core.Param(usize) {
130 .id = id,
131 .takes_value = p.takes_value != null,
132 .names = core.Names{
133 .bare = null,
134 .short = p.short,
135 .long = p.long,
136 },
137 };
138 }
139
140 for (command.sub_commands) |c, i| {
141 const id = i + command.params.len;
142 res[id] = core.Param(usize) {
143 .id = id,
144 .takes_value = false,
145 .names = core.Names.bare(c.name),
146 };
147 }
148
149 break :blk res;
150 };
151
152 var handled = comptime blk: {
153 var res: [command.params.len]bool = undefined;
154 for (command.params) |p, i| {
155 res[i] = !p.required;
156 }
157
158 break :blk res;
159 };
160
161 var pos: usize = 0;
162 var iter = core.Clap(usize).init(core_params, arg_iter, allocator);
163 defer iter.deinit();
164
165 arg_loop:
166 while (try iter.next()) |arg| : (pos += 1) {
167 inline for(command.params) |param, i| {
168 comptime const field = "result." ++ param.field;
169
170 if (arg.id == i and (param.position ?? pos) == pos) {
171 if (param.takes_value) |parser| {
172 try parser.parse(getFieldPtr(&result, field), ??arg.value);
173 } else {
174 getFieldPtr(&result, field).* = true;
175 }
176 handled[i] = true;
177 continue :arg_loop;
178 }
179 }
180
181 inline for(command.sub_commands) |c, i| {
182 comptime const field = "result." ++ c.field;
183 comptime var sub_command = c;
184 sub_command.parent = command;
185
186 if (arg.id == i + command.params.len) {
187 getFieldPtr(&result, field).* = try sub_command.parseHelper(&result, allocator, arg_iter);
188 continue :arg_loop;
189 }
190 }
191
192 return error.InvalidArgument;
193 }
194
195 return result.result;
196 }
197
198 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
199 var inst: Struct = undefined;
200 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
201 return @typeOf(&@field(inst, field));
202 };
203
204 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
205 }
206
207 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
208 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
209 return &@field(curr, field);
210 };
211
212 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
213 }
214};
215
216pub const Parser = struct {
217 const UnsafeFunction = &const void;
218
219 FieldType: type,
220 Errors: type,
221 func: UnsafeFunction,
222
223 pub fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
224 return Parser {
225 .FieldType = FieldType,
226 .Errors = Errors,
227 .func = @ptrCast(UnsafeFunction, func),
228 };
229 }
230
231 fn parse(comptime parser: Parser, field_ptr: TakePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
232 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
233 }
234
235 // TODO: This is a workaround, since we don't have pointer reform yet.
236 fn TakePtr(comptime T: type) type { return &T; }
237
238 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
239 return fn(&FieldType, []const u8) Errors!void;
240 }
241
242 pub fn int(comptime Int: type, comptime radix: u8) Parser {
243 const func = struct {
244 fn i(field_ptr: &Int, arg: []const u8) !void {
245 field_ptr.* = try fmt.parseInt(Int, arg, radix);
246 }
247 }.i;
248 return Parser.init(
249 Int,
250 @typeOf(func).ReturnType.ErrorSet,
251 func
252 );
253 }
254
255 const string = Parser.init(
256 []const u8,
257 error{},
258 struct {
259 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
260 field_ptr.* = arg;
261 }
262 }.s
263 );
264};