summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clap.zig415
1 files changed, 211 insertions, 204 deletions
diff --git a/clap.zig b/clap.zig
index bb08251..f664da0 100644
--- a/clap.zig
+++ b/clap.zig
@@ -20,7 +20,217 @@ pub fn Clap(comptime Result: type) type {
20 defaults: Result, 20 defaults: Result,
21 21
22 pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result { 22 pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result {
23 return clap.command.parse(Result, clap.defaults, arguments); 23 return parseCommand(CommandList { .command = clap.command, .prev = null }, clap.defaults, arguments);
24 }
25
26 const CommandList = struct {
27 command: &const Command,
28 prev: ?&const CommandList,
29 };
30
31 fn parseCommand(comptime list: &const CommandList, defaults: &const Result, arguments: []const []const u8) !Result {
32 const command = list.command;
33
34 const Arg = struct {
35 const Kind = enum { Long, Short, Value };
36
37 arg: []const u8,
38 kind: Kind,
39 after_eql: ?[]const u8,
40 };
41
42 const Iterator = struct {
43 index: usize,
44 slice: []const []const u8,
45
46 pub fn next(it: &this) ?[]const u8 {
47 if (it.index >= it.slice.len)
48 return null;
49
50 defer it.index += 1;
51 return it.slice[it.index];
52 }
53 };
54
55 // NOTE: For now, a bitfield is used to keep track of the required arguments.
56 // This limits the user to 128 required arguments, which should be more
57 // than enough.
58 var required = comptime blk: {
59 var required_index : u128 = 0;
60 var required_res : u128 = 0;
61 for (command.arguments) |option| {
62 if (option.required) {
63 required_res |= 0x1 << required_index;
64 required_index += 1;
65 }
66 }
67
68 break :blk required_res;
69 };
70
71 var result = *defaults;
72
73 var it = Iterator { .index = 0, .slice = arguments };
74 while (it.next()) |item| {
75 const arg_info = blk: {
76 var arg = item;
77 var kind = Arg.Kind.Value;
78
79 if (mem.startsWith(u8, arg, "--")) {
80 arg = arg[2..];
81 kind = Arg.Kind.Long;
82 } else if (mem.startsWith(u8, arg, "-")) {
83 arg = arg[1..];
84 kind = Arg.Kind.Short;
85 }
86
87 if (kind == Arg.Kind.Value)
88 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
89
90
91 if (mem.indexOfScalar(u8, arg, '=')) |index| {
92 break :blk Arg { .arg = arg[0..index], .kind = kind, .after_eql = arg[index + 1..] };
93 } else {
94 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
95 }
96 };
97 const arg = arg_info.arg;
98 const kind = arg_info.kind;
99 const after_eql = arg_info.after_eql;
100
101 success: {
102 switch (kind) {
103 // TODO: Handle subcommands
104 Arg.Kind.Value => {
105 var required_index = usize(0);
106 inline for (command.arguments) |option| {
107 defer if (option.required) required_index += 1;
108 if (option.short != null) continue;
109 if (option.long != null) continue;
110
111 try option.parse(&result, arg);
112 required = newRequired(option, required, required_index);
113 break :success;
114 }
115 },
116 Arg.Kind.Short => {
117 if (arg.len == 0) return error.FoundShortOptionWithNoName;
118 short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| {
119 var required_index = usize(0);
120 inline for (command.arguments) |option| {
121 defer if (option.required) required_index += 1;
122 const short = option.short ?? continue;
123 if (short_arg == short) {
124 if (option.takes_value) return error.OptionMissingValue;
125
126 *getFieldPtr(Result, &result, option.field) = true;
127 required = newRequired(option, required, required_index);
128 continue :short_arg_loop;
129 }
130 }
131
132 return error.InvalidArgument;
133 }
134
135 const last_arg = arg[arg.len - 1];
136 var required_index = usize(0);
137 inline for (command.arguments) |option| {
138 defer if (option.required) required_index += 1;
139 const short = option.short ?? continue;
140
141 if (last_arg == short) {
142 if (option.takes_value) {
143 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
144 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
145 } else {
146 *getFieldPtr(Result, &result, option.field) = true;
147 }
148
149 required = newRequired(option, required, required_index);
150 break :success;
151 }
152 }
153 },
154 Arg.Kind.Long => {
155 var required_index = usize(0);
156 inline for (command.arguments) |option| {
157 defer if (option.required) required_index += 1;
158 const long = option.long ?? continue;
159
160 if (mem.eql(u8, arg, long)) {
161 if (option.takes_value) {
162 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
163 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
164 } else {
165 *getFieldPtr(Result, &result, option.field) = true;
166 }
167
168 required = newRequired(option, required, required_index);
169 break :success;
170 }
171 }
172 }
173 }
174
175 return error.InvalidArgument;
176 }
177 }
178
179 if (required != 0) {
180 return error.RequiredArgumentWasntHandled;
181 }
182
183 return result;
184 }
185
186 fn FieldType(comptime T: type, comptime field: []const u8) type {
187 var i = usize(0);
188 inline while (i < @memberCount(T)) : (i += 1) {
189 if (mem.eql(u8, @memberName(T, i), field))
190 return @memberType(T, i);
191 }
192
193 @compileError("Field not found!");
194 }
195
196 fn getFieldPtr(comptime T: type, res: &T, comptime field: []const u8) &FieldType(T, field) {
197 return @intToPtr(&FieldType(T, field), @ptrToInt(res) + @offsetOf(T, field));
198 }
199
200 fn strToValue(comptime T: type, str: []const u8) !T {
201 const TypeId = builtin.TypeId;
202 switch (@typeId(T)) {
203 TypeId.Type, TypeId.Void, TypeId.NoReturn, TypeId.Pointer,
204 TypeId.Array, TypeId.Struct, TypeId.UndefinedLiteral,
205 TypeId.NullLiteral, TypeId.ErrorUnion, TypeId.ErrorSet,
206 TypeId.Union, TypeId.Fn, TypeId.Namespace, TypeId.Block,
207 TypeId.BoundFn, TypeId.ArgTuple, TypeId.Opaque, TypeId.Promise => @compileError("Type not supported!"),
208
209 TypeId.Bool => {
210 if (mem.eql(u8, "true", str))
211 return true;
212 if (mem.eql(u8, "false", str))
213 return false;
214
215 return error.CannotParseStringAsBool;
216 },
217 TypeId.Int, TypeId.IntLiteral => return fmt.parseInt(T, str, 10),
218 TypeId.Float, TypeId.FloatLiteral => @compileError("TODO: Implement str to float"),
219 TypeId.Nullable => {
220 if (mem.eql(u8, "null", str))
221 return null;
222
223 return strToValue(T.Child, str);
224 },
225 TypeId.Enum => @compileError("TODO: Implement str to enum"),
226 }
227 }
228
229 fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 {
230 if (argument.required)
231 return old_required & ~(u128(1) << u7(index));
232
233 return old_required;
24 } 234 }
25 235
26 pub const Builder = struct { 236 pub const Builder = struct {
@@ -82,209 +292,6 @@ pub const Command = struct {
82 arguments: []const Argument, 292 arguments: []const Argument,
83 sub_commands: []const Command, 293 sub_commands: []const Command,
84 294
85 pub fn parse(comptime command: &const Command, comptime Result: type, defaults: &const Result, arguments: []const []const u8) !Result {
86 const Arg = struct {
87 const Kind = enum { Long, Short, Value };
88
89 arg: []const u8,
90 kind: Kind,
91 after_eql: ?[]const u8,
92 };
93
94 const Iterator = struct {
95 index: usize,
96 slice: []const []const u8,
97
98 pub fn next(it: &this) ?[]const u8 {
99 if (it.index >= it.slice.len)
100 return null;
101
102 defer it.index += 1;
103 return it.slice[it.index];
104 }
105 };
106
107 // NOTE: For now, a bitfield is used to keep track of the required arguments.
108 // This limits the user to 128 required arguments, which should be more
109 // than enough.
110 var required = comptime blk: {
111 var required_index : u128 = 0;
112 var required_res : u128 = 0;
113 for (command.arguments) |option| {
114 if (option.required) {
115 required_res |= 0x1 << required_index;
116 required_index += 1;
117 }
118 }
119
120 break :blk required_res;
121 };
122
123 var result = *defaults;
124
125 var it = Iterator { .index = 0, .slice = arguments };
126 while (it.next()) |item| {
127 const arg_info = blk: {
128 var arg = item;
129 var kind = Arg.Kind.Value;
130
131 if (mem.startsWith(u8, arg, "--")) {
132 arg = arg[2..];
133 kind = Arg.Kind.Long;
134 } else if (mem.startsWith(u8, arg, "-")) {
135 arg = arg[1..];
136 kind = Arg.Kind.Short;
137 }
138
139 if (kind == Arg.Kind.Value)
140 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
141
142
143 if (mem.indexOfScalar(u8, arg, '=')) |index| {
144 break :blk Arg { .arg = arg[0..index], .kind = kind, .after_eql = arg[index + 1..] };
145 } else {
146 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
147 }
148 };
149 const arg = arg_info.arg;
150 const kind = arg_info.kind;
151 const after_eql = arg_info.after_eql;
152
153 success: {
154 switch (kind) {
155 // TODO: Handle subcommands
156 Arg.Kind.Value => {
157 var required_index = usize(0);
158 inline for (command.arguments) |option| {
159 defer if (option.required) required_index += 1;
160 if (option.short != null) continue;
161 if (option.long != null) continue;
162
163 try option.parse(&result, arg);
164 required = newRequired(option, required, required_index);
165 break :success;
166 }
167 },
168 Arg.Kind.Short => {
169 if (arg.len == 0) return error.FoundShortOptionWithNoName;
170 short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| {
171 var required_index = usize(0);
172 inline for (command.arguments) |option| {
173 defer if (option.required) required_index += 1;
174 const short = option.short ?? continue;
175 if (short_arg == short) {
176 if (option.takes_value) return error.OptionMissingValue;
177
178 *getFieldPtr(Result, &result, option.field) = true;
179 required = newRequired(option, required, required_index);
180 continue :short_arg_loop;
181 }
182 }
183
184 return error.InvalidArgument;
185 }
186
187 const last_arg = arg[arg.len - 1];
188 var required_index = usize(0);
189 inline for (command.arguments) |option| {
190 defer if (option.required) required_index += 1;
191 const short = option.short ?? continue;
192
193 if (last_arg == short) {
194 if (option.takes_value) {
195 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
196 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
197 } else {
198 *getFieldPtr(Result, &result, option.field) = true;
199 }
200
201 required = newRequired(option, required, required_index);
202 break :success;
203 }
204 }
205 },
206 Arg.Kind.Long => {
207 var required_index = usize(0);
208 inline for (command.arguments) |option| {
209 defer if (option.required) required_index += 1;
210 const long = option.long ?? continue;
211
212 if (mem.eql(u8, arg, long)) {
213 if (option.takes_value) {
214 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
215 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
216 } else {
217 *getFieldPtr(Result, &result, option.field) = true;
218 }
219
220 required = newRequired(option, required, required_index);
221 break :success;
222 }
223 }
224 }
225 }
226
227 return error.InvalidArgument;
228 }
229 }
230
231 if (required != 0) {
232 return error.RequiredArgumentWasntHandled;
233 }
234
235 return result;
236 }
237
238 fn FieldType(comptime Result: type, comptime field: []const u8) type {
239 var i = usize(0);
240 inline while (i < @memberCount(Result)) : (i += 1) {
241 if (mem.eql(u8, @memberName(Result, i), field))
242 return @memberType(Result, i);
243 }
244
245 @compileError("Field not found!");
246 }
247
248 fn getFieldPtr(comptime Result: type, res: &Result, comptime field: []const u8) &FieldType(Result, field) {
249 return @intToPtr(&FieldType(Result, field), @ptrToInt(res) + @offsetOf(Result, field));
250 }
251
252 fn strToValue(comptime Result: type, str: []const u8) !Result {
253 const TypeId = builtin.TypeId;
254 switch (@typeId(Result)) {
255 TypeId.Type, TypeId.Void, TypeId.NoReturn, TypeId.Pointer,
256 TypeId.Array, TypeId.Struct, TypeId.UndefinedLiteral,
257 TypeId.NullLiteral, TypeId.ErrorUnion, TypeId.ErrorSet,
258 TypeId.Union, TypeId.Fn, TypeId.Namespace, TypeId.Block,
259 TypeId.BoundFn, TypeId.ArgTuple, TypeId.Opaque, TypeId.Promise => @compileError("Type not supported!"),
260
261 TypeId.Bool => {
262 if (mem.eql(u8, "true", str))
263 return true;
264 if (mem.eql(u8, "false", str))
265 return false;
266
267 return error.CannotParseStringAsBool;
268 },
269 TypeId.Int, TypeId.IntLiteral => return fmt.parseInt(Result, str, 10),
270 TypeId.Float, TypeId.FloatLiteral => @compileError("TODO: Implement str to float"),
271 TypeId.Nullable => {
272 if (mem.eql(u8, "null", str))
273 return null;
274
275 return strToValue(Result.Child, str);
276 },
277 TypeId.Enum => @compileError("TODO: Implement str to enum"),
278 }
279 }
280
281 fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 {
282 if (argument.required)
283 return old_required & ~(u128(1) << u7(index));
284
285 return old_required;
286 }
287
288 pub const Builder = struct { 295 pub const Builder = struct {
289 result: Command, 296 result: Command,
290 297