summaryrefslogtreecommitdiff
path: root/src/core.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/core.zig')
-rw-r--r--src/core.zig372
1 files changed, 372 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}