diff options
| author | 2018-05-19 13:14:50 +0200 | |
|---|---|---|
| committer | 2018-05-19 13:14:50 +0200 | |
| commit | 822acb4cd1fb7a4f05e7b0f5a520734b2155da40 (patch) | |
| tree | d007386655653b2f12c2e65b5ade37e960a8888e | |
| parent | Started work one a simpler core api (diff) | |
| download | zig-clap-822acb4cd1fb7a4f05e7b0f5a520734b2155da40.tar.gz zig-clap-822acb4cd1fb7a4f05e7b0f5a520734b2155da40.tar.xz zig-clap-822acb4cd1fb7a4f05e7b0f5a520734b2155da40.zip | |
Finished the first draft of the core
* It haven't been tested yet
| -rw-r--r-- | core.zig | 197 |
1 files changed, 175 insertions, 22 deletions
| @@ -2,8 +2,28 @@ const std = @import("std"); | |||
| 2 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | 3 | ||
| 4 | const os = std.os; | 4 | const os = std.os; |
| 5 | const heap = std.heap; | ||
| 5 | const is_windows = builtin.os == Os.windows; | 6 | const 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. | ||
| 7 | pub fn Param(comptime Id: type) type { | 27 | pub 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 | ||
| 41 | pub fn Arg(comptime Id: type) type { | 57 | pub 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 | ||
| 49 | pub 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. | ||
| 53 | pub fn Iterator(comptime Id: type) type { | 77 | pub 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 | } |