summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core.zig197
1 files changed, 175 insertions, 22 deletions
diff --git a/core.zig b/core.zig
index 087de9b..1839171 100644
--- a/core.zig
+++ b/core.zig
@@ -2,8 +2,28 @@ const std = @import("std");
2const builtin = @import("builtin"); 2const builtin = @import("builtin");
3 3
4const os = std.os; 4const os = std.os;
5const heap = std.heap;
5const is_windows = builtin.os == Os.windows; 6const is_windows = builtin.os == Os.windows;
6 7
8/// Represents a parameter for the command line.
9/// Parameters come in three kinds:
10/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
11/// * They can take a value three different ways.
12/// * "-a value"
13/// * "-a=value"
14/// * "-avalue"
15/// * They chain if they don't take values: "-abc".
16/// * The last given parameter can take a value in the same way that a single parameter can:
17/// * "-abc value"
18/// * "-abc=value"
19/// * "-abcvalue"
20/// * Long ("--long-arg"): Should be used for less common parameters, or when no single character
21/// can describe the paramter.
22/// * They can take a value two different ways.
23/// * "--long-arg value"
24/// * "--long-arg=value"
25/// * Value ("some-value"): Should be used as the primary of the program, like a filename or an
26/// expression to parse.
7pub fn Param(comptime Id: type) type { 27pub fn Param(comptime Id: type) type {
8 return struct { 28 return struct {
9 const Self = this; 29 const Self = this;
@@ -11,11 +31,9 @@ pub fn Param(comptime Id: type) type {
11 id: Id, 31 id: Id,
12 short: ?u8, 32 short: ?u8,
13 long: ?[]const u8, 33 long: ?[]const u8,
14 index: ?usize,
15 takes_value: bool, 34 takes_value: bool,
16 required: bool,
17 35
18 /// Initialize a parameter. 36 /// Initialize a ::Param.
19 /// If ::name.len == 0, then it's a value parameter: "some-command value". 37 /// If ::name.len == 0, then it's a value parameter: "some-command value".
20 /// If ::name.len == 1, then it's a short parameter: "some-command -s". 38 /// If ::name.len == 1, then it's a short parameter: "some-command -s".
21 /// If ::name.len > 1, then it's a long parameter: "some-command --long". 39 /// If ::name.len > 1, then it's a long parameter: "some-command --long".
@@ -24,9 +42,7 @@ pub fn Param(comptime Id: type) type {
24 .id = id, 42 .id = id,
25 .short = if (name.len == 1) name[0] else null, 43 .short = if (name.len == 1) name[0] else null,
26 .long = if (name.len > 1) name else null, 44 .long = if (name.len > 1) name else null,
27 .index = null,
28 .takes_value = false, 45 .takes_value = false,
29 .required = false,
30 }; 46 };
31 } 47 }
32 48
@@ -40,39 +56,176 @@ pub fn Param(comptime Id: type) type {
40 56
41pub fn Arg(comptime Id: type) type { 57pub fn Arg(comptime Id: type) type {
42 return struct { 58 return struct {
59 const Self = this;
60
43 id: Id, 61 id: Id,
44 value: ?[]const u8,
45 };
46}
47 62
63 /// ::Iterator owns ::value. On windows, this means that when you call ::Iterator.deinit
64 /// ::value is freed.
65 value: ?[]const u8,
48 66
49pub fn args() ArgIterator { 67 pub fn init(id: Id, value: ?[]const u8) Self {
50 return ArgIterator.init(); 68 return Self {
69 .id = id,
70 .value = value,
71 };
72 }
73 };
51} 74}
52 75
76/// A ::CustomIterator with a default Windows buffer size.
53pub fn Iterator(comptime Id: type) type { 77pub fn Iterator(comptime Id: type) type {
54 return struct { 78 return struct {
55 const Self = this; 79 const Self = this;
56 const Buffer = if (is_windows) [1024 * 2]u8 else void;
57 80
58 windows_buffer: Buffer, 81 const State = union(enum) {
82 Normal,
83 Chaining: Chaining,
84
85 const Chaining = struct {
86 arg: []const u8,
87 index: usize,
88 next: &const Param,
89 };
90 };
91
92 arena: &heap.ArenaAllocator,
59 params: Param(Id), 93 params: Param(Id),
60 args: os.ArgIterator, 94 args: os.ArgIterator,
61 exe: []const u8, 95 state: State,
96 command: []const u8,
62 97
63 pub fn init(params: []const Param(Id)) Self { 98 pub fn init(params: []const Param(Id), allocator: &mem.Allocator) !Self {
64 return Self { 99 var res = Self {
100 .allocator = heap.ArenaAllocator.init(allocator),
65 .params = params, 101 .params = params,
66 . 102 .args = os.args(),
103 .command = undefined,
67 }; 104 };
105 res.command = try res.innerNext();
106
107 return res;
108 }
109
110 pub fn deinit(iter: &const Self) void {
111 iter.arena.deinit();
112 }
113
114 /// Get the next ::Arg that matches a ::Param.
115 pub fn next(iter: &Self) !?Arg(Id) {
116 const ArgInfo = struct {
117 const Kind = enum { Long, Short, Value };
118
119 arg: []const u8,
120 kind: Kind,
121 };
122
123 switch (iter.state) {
124 State.Normal => {
125 const full_arg = (try iter.innerNext()) ?? return null;
126 const arg_info = blk: {
127 var arg = full_arg;
128 var kind = ArgInfo.Kind.Value;
129
130 if (mem.startsWith(u8, arg, "--")) {
131 arg = arg[2..];
132 kind = Arg.Kind.Long;
133 } else if (mem.startsWith(u8, arg, "-")) {
134 arg = arg[1..];
135 kind = Arg.Kind.Short;
136 }
137
138 if (arg.len == 0)
139 return error.ArgWithNoName;
140
141 break :blk ArgInfo { .arg = arg, .kind = kind };
142 };
143
144 const arg = arg_info.arg;
145 const kind = arg_info.kind;
146
147 for (iter.params) |*param| {
148 switch (kind) {
149 Arg.Kind.Long => {
150 const long = param.long ?? continue;
151 if (!mem.eql(u8, arg, long))
152 continue;
153 if (!param.takes_value)
154 return Arg(Id).init(param.id, null);
155
156 const value = (try iter.innerNext()) ?? return error.MissingValue;
157 return Arg(Id).init(param.id, value);
158 },
159 Arg.Kind.Short => {
160 const short = param.short ?? continue;
161 if (short != arg[0])
162 continue;
163
164 return try iter.chainging(State.Chaining {
165 .arg = full_arg,
166 .index = (full_arg.len - arg.len) + 1,
167 .next = param,
168 });
169 },
170 Arg.Kind.Value => {
171 if (param.long) |_| continue;
172 if (param.short) |_| continue;
173
174 return Arg(Id).init(param.id, arg);
175 }
176 }
177 }
178 },
179 State.Chaining => |state| return try iter.chainging(state),
180 }
181 }
182
183 fn chainging(iter: &const Self, state: &const State.Chaining) !?Arg(Id) {
184 const arg = state.arg;
185 const index = state.index;
186 const curr_param = state.param;
187
188 if (curr_param.takes_value) {
189 if (arg.len <= index) {
190 const value = (try iter.innerNext()) ?? return error.MissingValue;
191 return Arg(Id).init(curr_param.id, value);
192 }
193
194 if (arg[index] == '=') {
195 return Arg(Id).init(curr_param.id, arg[index + 1..]);
196 }
197
198 return Arg(Id).init(curr_param.id, arg[index..]);
199 }
200
201 if (arg.len <= index) {
202 iter.state = State.Normal;
203 return Arg(Id).init(curr_param.id, null);
204 }
205
206 for (iter.params) |*param| {
207 const short = param.short ?? continue;
208 if (short != arg[index])
209 continue;
210
211 iter.State = State { .Chaining = State.Chaining {
212 .arg = arg,
213 .index = index + 1,
214 .param = param,
215 }};
216 return Arg(Id).init(curr_param.id, null);
217 }
218
219 // This actually returns an error for the next argument.
220 return error.InvalidArgument;
68 } 221 }
69 222
70 fn innerNext(iter: &Self) ?[]const u8 { 223 fn innerNext(iter: &Self) os.ArgIterator.NextError!?[]const u8 {
71 //if (builtin.os == Os.windows) { 224 if (builtin.os == Os.windows) {
72 // return iter.args.next(allocator); 225 return try iter.args.next(&iter.arena.allocator);
73 //} else { 226 } else {
74 // return iter.args.nextPosix(); 227 return iter.args.nextPosix();
75 //} 228 }
76 } 229 }
77 } 230 }
78} 231}