diff options
| -rw-r--r-- | clap.zig | 415 |
1 files changed, 211 insertions, 204 deletions
| @@ -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 | ||