diff options
Diffstat (limited to '')
| -rw-r--r-- | clap.zig | 249 |
1 files changed, 200 insertions, 49 deletions
| @@ -3,6 +3,7 @@ const std = @import("std"); | |||
| 3 | const debug = std.debug; | 3 | const debug = std.debug; |
| 4 | const io = std.io; | 4 | const io = std.io; |
| 5 | const mem = std.mem; | 5 | const mem = std.mem; |
| 6 | const testing = std.testing; | ||
| 6 | 7 | ||
| 7 | pub const args = @import("src/args.zig"); | 8 | pub const args = @import("src/args.zig"); |
| 8 | 9 | ||
| @@ -53,10 +54,166 @@ pub fn Param(comptime Id: type) type { | |||
| 53 | }; | 54 | }; |
| 54 | } | 55 | } |
| 55 | 56 | ||
| 57 | /// Takes a string and parses it to a Param(Help). | ||
| 58 | /// This is the reverse of 'help2' but for at single parameter only. | ||
| 59 | pub fn parseParam(line: []const u8) !Param(Help) { | ||
| 60 | var res = Param(Help){ | ||
| 61 | .id = Help{ | ||
| 62 | .msg = line[0..0], | ||
| 63 | .value = line[0..0], | ||
| 64 | }, | ||
| 65 | }; | ||
| 66 | |||
| 67 | var it = mem.tokenize(line, " \t"); | ||
| 68 | var param_str = it.next() orelse return error.NoParamFound; | ||
| 69 | if (!mem.startsWith(u8, param_str, "--") and mem.startsWith(u8, param_str, "-")) { | ||
| 70 | const found_comma = param_str[param_str.len - 1] == ','; | ||
| 71 | if (found_comma) | ||
| 72 | param_str = param_str[0..param_str.len - 1]; | ||
| 73 | |||
| 74 | switch (param_str.len) { | ||
| 75 | 1 => return error.InvalidShortParam, | ||
| 76 | 2 => { | ||
| 77 | res.names.short = param_str[1]; | ||
| 78 | if (!found_comma) { | ||
| 79 | res.id.msg = mem.trim(u8, it.rest(), " \t"); | ||
| 80 | return res; | ||
| 81 | } | ||
| 82 | }, | ||
| 83 | else => { | ||
| 84 | res.names.short = param_str[1]; | ||
| 85 | if (param_str[2] != '=') | ||
| 86 | return error.InvalidShortParam; | ||
| 87 | |||
| 88 | res.id.value = param_str[3..]; | ||
| 89 | res.takes_value = true; | ||
| 90 | |||
| 91 | if (found_comma) | ||
| 92 | return error.TrailingComma; | ||
| 93 | |||
| 94 | res.id.msg = mem.trim(u8, it.rest(), " \t"); | ||
| 95 | return res; | ||
| 96 | }, | ||
| 97 | } | ||
| 98 | |||
| 99 | param_str = it.next() orelse return error.NoParamFound; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (mem.startsWith(u8, param_str, "--")) { | ||
| 103 | res.names.long = param_str[2..]; | ||
| 104 | if (mem.indexOfScalar(u8, param_str, '=')) |eql_index| { | ||
| 105 | res.names.long = param_str[2..eql_index]; | ||
| 106 | res.id.value = param_str[eql_index + 1 ..]; | ||
| 107 | res.takes_value = true; | ||
| 108 | } | ||
| 109 | |||
| 110 | if (param_str[param_str.len - 1] == ',') | ||
| 111 | return error.TrailingComma; | ||
| 112 | |||
| 113 | res.id.msg = mem.trim(u8, it.rest(), " \t"); | ||
| 114 | return res; | ||
| 115 | } | ||
| 116 | |||
| 117 | return error.NoParamFound; | ||
| 118 | } | ||
| 119 | |||
| 120 | test "parseParam" { | ||
| 121 | var text: []const u8 = "-s, --long=value Help text"; | ||
| 122 | testing.expectEqual(Param(Help){ | ||
| 123 | .id = Help{ | ||
| 124 | .msg = text[17..], | ||
| 125 | .value = text[11..16], | ||
| 126 | }, | ||
| 127 | .names = Names{ | ||
| 128 | .short = 's', | ||
| 129 | .long = text[6..10], | ||
| 130 | }, | ||
| 131 | .takes_value = true, | ||
| 132 | }, try parseParam(text)); | ||
| 133 | |||
| 134 | text = "--long=value Help text"; | ||
| 135 | testing.expectEqual(Param(Help){ | ||
| 136 | .id = Help{ | ||
| 137 | .msg = text[13..], | ||
| 138 | .value = text[7..12], | ||
| 139 | }, | ||
| 140 | .names = Names{ | ||
| 141 | .short = null, | ||
| 142 | .long = text[2..6], | ||
| 143 | }, | ||
| 144 | .takes_value = true, | ||
| 145 | }, try parseParam(text)); | ||
| 146 | |||
| 147 | text = "-s=value Help text"; | ||
| 148 | testing.expectEqual(Param(Help){ | ||
| 149 | .id = Help{ | ||
| 150 | .msg = text[9..], | ||
| 151 | .value = text[3..8], | ||
| 152 | }, | ||
| 153 | .names = Names{ | ||
| 154 | .short = 's', | ||
| 155 | .long = null, | ||
| 156 | }, | ||
| 157 | .takes_value = true, | ||
| 158 | }, try parseParam(text)); | ||
| 159 | |||
| 160 | text = "-s, --long Help text"; | ||
| 161 | testing.expectEqual(Param(Help){ | ||
| 162 | .id = Help{ | ||
| 163 | .msg = text[11..], | ||
| 164 | .value = text[0..0], | ||
| 165 | }, | ||
| 166 | .names = Names{ | ||
| 167 | .short = 's', | ||
| 168 | .long = text[6..10], | ||
| 169 | }, | ||
| 170 | .takes_value = false, | ||
| 171 | }, try parseParam(text)); | ||
| 172 | |||
| 173 | text = "-s Help text"; | ||
| 174 | testing.expectEqual(Param(Help){ | ||
| 175 | .id = Help{ | ||
| 176 | .msg = text[3..], | ||
| 177 | .value = text[0..0], | ||
| 178 | }, | ||
| 179 | .names = Names{ | ||
| 180 | .short = 's', | ||
| 181 | .long = null, | ||
| 182 | }, | ||
| 183 | .takes_value = false, | ||
| 184 | }, try parseParam(text)); | ||
| 185 | |||
| 186 | text = "--long Help text"; | ||
| 187 | testing.expectEqual(Param(Help){ | ||
| 188 | .id = Help{ | ||
| 189 | .msg = text[7..], | ||
| 190 | .value = text[0..0], | ||
| 191 | }, | ||
| 192 | .names = Names{ | ||
| 193 | .short = null, | ||
| 194 | .long = text[2..6], | ||
| 195 | }, | ||
| 196 | .takes_value = false, | ||
| 197 | }, try parseParam(text)); | ||
| 198 | |||
| 199 | testing.expectError(error.NoParamFound, parseParam("Help")); | ||
| 200 | testing.expectError(error.TrailingComma, parseParam("--long, Help")); | ||
| 201 | testing.expectError(error.TrailingComma, parseParam("--long=value, Help")); | ||
| 202 | testing.expectError(error.NoParamFound, parseParam("-s, Help")); | ||
| 203 | testing.expectError(error.TrailingComma, parseParam("-s=value, Help")); | ||
| 204 | testing.expectError(error.InvalidShortParam, parseParam("-ss Help")); | ||
| 205 | testing.expectError(error.InvalidShortParam, parseParam("-ss=value Help")); | ||
| 206 | testing.expectError(error.InvalidShortParam, parseParam("- Help")); | ||
| 207 | } | ||
| 208 | |||
| 209 | |||
| 210 | |||
| 56 | /// Will print a help message in the following format: | 211 | /// Will print a help message in the following format: |
| 57 | /// -s, --long=value_text help_text | 212 | /// -s, --long=value_text help_text |
| 58 | /// -s, help_text | 213 | /// -s, help_text |
| 214 | /// -s=value_text help_text | ||
| 59 | /// --long help_text | 215 | /// --long help_text |
| 216 | /// --long=value_text help_text | ||
| 60 | pub fn helpFull( | 217 | pub fn helpFull( |
| 61 | stream: var, | 218 | stream: var, |
| 62 | comptime Id: type, | 219 | comptime Id: type, |
| @@ -152,18 +309,22 @@ pub fn helpEx( | |||
| 152 | ); | 309 | ); |
| 153 | } | 310 | } |
| 154 | 311 | ||
| 155 | /// A wrapper around helpEx that takes a Param([]const u8) and uses the string id | 312 | pub const Help = struct { |
| 156 | /// as the help text for each paramter. | 313 | msg: []const u8 = "", |
| 157 | pub fn help(stream: var, params: []const Param([]const u8)) !void { | 314 | value: []const u8 = "", |
| 158 | try helpEx(stream, []const u8, params, getHelpSimple, getValueSimple); | 315 | }; |
| 316 | |||
| 317 | /// A wrapper around helpEx that takes a Param(Help). | ||
| 318 | pub fn help(stream: var, params: []const Param(Help)) !void { | ||
| 319 | try helpEx(stream, Help, params, getHelpSimple, getValueSimple); | ||
| 159 | } | 320 | } |
| 160 | 321 | ||
| 161 | fn getHelpSimple(param: Param([]const u8)) []const u8 { | 322 | fn getHelpSimple(param: Param(Help)) []const u8 { |
| 162 | return param.id; | 323 | return param.id.msg; |
| 163 | } | 324 | } |
| 164 | 325 | ||
| 165 | fn getValueSimple(param: Param([]const u8)) []const u8 { | 326 | fn getValueSimple(param: Param(Help)) []const u8 { |
| 166 | return "VALUE"; | 327 | return param.id.value; |
| 167 | } | 328 | } |
| 168 | 329 | ||
| 169 | test "clap.help" { | 330 | test "clap.help" { |
| @@ -171,54 +332,44 @@ test "clap.help" { | |||
| 171 | var slice_stream = io.SliceOutStream.init(buf[0..]); | 332 | var slice_stream = io.SliceOutStream.init(buf[0..]); |
| 172 | try help( | 333 | try help( |
| 173 | &slice_stream.stream, | 334 | &slice_stream.stream, |
| 174 | [_]Param([]const u8){ | 335 | comptime [_]Param(Help){ |
| 175 | Param([]const u8){ | 336 | parseParam("-a Short flag. ") catch unreachable, |
| 176 | .id = "Short flag.", | 337 | parseParam("-b=V1 Short option.") catch unreachable, |
| 177 | .names = Names{ .short = 'a' }, | 338 | parseParam("--aa Long flag. ") catch unreachable, |
| 178 | }, | 339 | parseParam("--bb=V2 Long option. ") catch unreachable, |
| 179 | Param([]const u8){ | 340 | parseParam("-c, --cc Both flag. ") catch unreachable, |
| 180 | .id = "Short option.", | 341 | parseParam("-d, --dd=V3 Both option. ") catch unreachable, |
| 181 | .names = Names{ .short = 'b' }, | 342 | Param(Help){ |
| 182 | .takes_value = true, | 343 | .id = Help{ |
| 183 | }, | 344 | .msg = "Positional. This should not appear in the help message.", |
| 184 | Param([]const u8){ | 345 | }, |
| 185 | .id = "Long flag.", | ||
| 186 | .names = Names{ .long = "aa" }, | ||
| 187 | }, | ||
| 188 | Param([]const u8){ | ||
| 189 | .id = "Long option.", | ||
| 190 | .names = Names{ .long = "bb" }, | ||
| 191 | .takes_value = true, | ||
| 192 | }, | ||
| 193 | Param([]const u8){ | ||
| 194 | .id = "Both flag.", | ||
| 195 | .names = Names{ .short = 'c', .long = "cc" }, | ||
| 196 | }, | ||
| 197 | Param([]const u8){ | ||
| 198 | .id = "Both option.", | ||
| 199 | .names = Names{ .short = 'd', .long = "dd" }, | ||
| 200 | .takes_value = true, | ||
| 201 | }, | ||
| 202 | Param([]const u8){ | ||
| 203 | .id = "Positional. This should not appear in the help message.", | ||
| 204 | .takes_value = true, | 346 | .takes_value = true, |
| 205 | }, | 347 | }, |
| 206 | }, | 348 | }, |
| 207 | ); | 349 | ); |
| 208 | 350 | ||
| 209 | const expected = "" ++ | 351 | const expected = "" ++ |
| 210 | "\t-a \tShort flag.\n" ++ | 352 | "\t-a \tShort flag.\n" ++ |
| 211 | "\t-b=VALUE \tShort option.\n" ++ | 353 | "\t-b=V1 \tShort option.\n" ++ |
| 212 | "\t --aa \tLong flag.\n" ++ | 354 | "\t --aa \tLong flag.\n" ++ |
| 213 | "\t --bb=VALUE\tLong option.\n" ++ | 355 | "\t --bb=V2\tLong option.\n" ++ |
| 214 | "\t-c, --cc \tBoth flag.\n" ++ | 356 | "\t-c, --cc \tBoth flag.\n" ++ |
| 215 | "\t-d, --dd=VALUE\tBoth option.\n"; | 357 | "\t-d, --dd=V3\tBoth option.\n"; |
| 216 | 358 | ||
| 217 | if (!mem.eql(u8, slice_stream.getWritten(), expected)) { | 359 | const actual = slice_stream.getWritten(); |
| 218 | debug.warn("============ Expected ============\n"); | 360 | if (!mem.eql(u8, actual, expected)) { |
| 361 | debug.warn("\n============ Expected ============\n"); | ||
| 219 | debug.warn("{}", expected); | 362 | debug.warn("{}", expected); |
| 220 | debug.warn("============= Actual =============\n"); | 363 | debug.warn("============= Actual =============\n"); |
| 221 | debug.warn("{}", slice_stream.getWritten()); | 364 | debug.warn("{}", actual); |
| 222 | return error.NoMatch; | 365 | |
| 366 | var buffer: [1024 * 2]u8 = undefined; | ||
| 367 | var fba = std.heap.FixedBufferAllocator.init(&buffer); | ||
| 368 | |||
| 369 | debug.warn("============ Expected (escaped) ============\n"); | ||
| 370 | debug.warn("{x}\n", expected); | ||
| 371 | debug.warn("============ Actual (escaped) ============\n"); | ||
| 372 | debug.warn("{x}\n", actual); | ||
| 373 | testing.expect(false); | ||
| 223 | } | 374 | } |
| 224 | } | 375 | } |