| 1 | #include "test-tool.h" |
| 2 | #include "parse-options.h" |
| 3 | #include "strbuf.h" |
| 4 | #include "string-list.h" |
| 5 | #include "trace2.h" |
| 6 | |
| 7 | static int boolean = 0; |
| 8 | static int integer = 0; |
| 9 | static unsigned long unsigned_integer = 0; |
| 10 | static timestamp_t timestamp; |
| 11 | static int abbrev = 7; |
| 12 | static int verbose = -1; /* unspecified */ |
| 13 | static int dry_run = 0, quiet = 0; |
| 14 | static char *string = NULL; |
| 15 | static char *file = NULL; |
| 16 | static int ambiguous; |
| 17 | |
| 18 | static struct { |
| 19 | int called; |
| 20 | const char *arg; |
| 21 | int unset; |
| 22 | } length_cb; |
| 23 | |
| 24 | static int mode34_callback(const struct option *opt, const char *arg, int unset) |
| 25 | { |
| 26 | if (unset) |
| 27 | *(int *)opt->value = 0; |
| 28 | else if (!strcmp(arg, "3")) |
| 29 | *(int *)opt->value = 3; |
| 30 | else if (!strcmp(arg, "4")) |
| 31 | *(int *)opt->value = 4; |
| 32 | else |
| 33 | return error("invalid value for '%s': '%s'", "--mode34", arg); |
| 34 | return 0; |
| 35 | } |
| 36 | |
| 37 | static int length_callback(const struct option *opt, const char *arg, int unset) |
| 38 | { |
| 39 | length_cb.called = 1; |
| 40 | length_cb.arg = arg; |
| 41 | length_cb.unset = unset; |
| 42 | |
| 43 | if (unset) |
| 44 | return 1; /* do not support unset */ |
| 45 | |
| 46 | *(int *)opt->value = strlen(arg); |
| 47 | return 0; |
| 48 | } |
| 49 | |
| 50 | static int number_callback(const struct option *opt, const char *arg, int unset) |
| 51 | { |
| 52 | BUG_ON_OPT_NEG(unset); |
| 53 | *(int *)opt->value = strtol(arg, NULL, 10); |
| 54 | return 0; |
| 55 | } |
| 56 | |
| 57 | static int collect_expect(const struct option *opt, const char *arg, int unset) |
| 58 | { |
| 59 | struct string_list *expect; |
| 60 | struct string_list_item *item; |
| 61 | struct strbuf label = STRBUF_INIT; |
| 62 | const char *colon; |
| 63 | |
| 64 | if (!arg || unset) |
| 65 | die("malformed --expect option"); |
| 66 | |
| 67 | expect = (struct string_list *)opt->value; |
| 68 | colon = strchr(arg, ':'); |
| 69 | if (!colon) |
| 70 | die("malformed --expect option, lacking a colon"); |
| 71 | strbuf_add(&label, arg, colon - arg); |
| 72 | item = string_list_insert(expect, strbuf_detach(&label, NULL)); |
| 73 | if (item->util) |
| 74 | die("malformed --expect option, duplicate %s", label.buf); |
| 75 | item->util = (void *)arg; |
| 76 | return 0; |
| 77 | } |
| 78 | |
| 79 | __attribute__((format (printf,3,4))) |
| 80 | static void show(struct string_list *expect, int *status, const char *fmt, ...) |
| 81 | { |
| 82 | struct string_list_item *item; |
| 83 | struct strbuf buf = STRBUF_INIT; |
| 84 | va_list args; |
| 85 | |
| 86 | va_start(args, fmt); |
| 87 | strbuf_vaddf(&buf, fmt, args); |
| 88 | va_end(args); |
| 89 | |
| 90 | if (!expect->nr) |
| 91 | printf("%s\n", buf.buf); |
| 92 | else { |
| 93 | char *colon = strchr(buf.buf, ':'); |
| 94 | if (!colon) |
| 95 | die("malformed output format, output lacking colon: %s", fmt); |
| 96 | *colon = '\0'; |
| 97 | item = string_list_lookup(expect, buf.buf); |
| 98 | *colon = ':'; |
| 99 | if (!item) |
| 100 | ; /* not among entries being checked */ |
| 101 | else { |
| 102 | if (strcmp((const char *)item->util, buf.buf)) { |
| 103 | printf("-%s\n", (char *)item->util); |
| 104 | printf("+%s\n", buf.buf); |
| 105 | *status = 1; |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | strbuf_release(&buf); |
| 110 | } |
| 111 | |
| 112 | int cmd__parse_options(int argc, const char **argv) |
| 113 | { |
| 114 | const char *prefix = "prefix/"; |
| 115 | const char *usage[] = { |
| 116 | "test-tool parse-options <options>", |
| 117 | "", |
| 118 | "A helper function for the parse-options API.", |
| 119 | NULL |
| 120 | }; |
| 121 | struct string_list expect = STRING_LIST_INIT_NODUP; |
| 122 | struct string_list list = STRING_LIST_INIT_NODUP; |
| 123 | uint16_t u16 = 0; |
| 124 | int16_t i16 = 0; |
| 125 | |
| 126 | struct option options[] = { |
| 127 | OPT_BOOL(0, "yes", &boolean, "get a boolean"), |
| 128 | OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), |
| 129 | { |
| 130 | .type = OPTION_SET_INT, |
| 131 | .short_name = 'B', |
| 132 | .long_name = "no-fear", |
| 133 | .value = &boolean, |
| 134 | .precision = sizeof(boolean), |
| 135 | .help = "be brave", |
| 136 | .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, |
| 137 | .defval = 1, |
| 138 | }, |
| 139 | OPT_COUNTUP('b', "boolean", &boolean, "increment by one"), |
| 140 | OPT_BIT('4', "or4", &boolean, |
| 141 | "bitwise-or boolean with ...0100", 4), |
| 142 | OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), |
| 143 | OPT_GROUP(""), |
| 144 | OPT_INTEGER('i', "integer", &integer, "get a integer"), |
| 145 | OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"), |
| 146 | OPT_INTEGER('j', NULL, &integer, "get a integer, too"), |
| 147 | OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"), |
| 148 | OPT_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"), |
| 149 | OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), |
| 150 | OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), |
| 151 | OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), |
| 152 | { |
| 153 | .type = OPTION_CALLBACK, |
| 154 | .long_name = "mode34", |
| 155 | .value = &integer, |
| 156 | .precision = sizeof(integer), |
| 157 | .argh = "(3|4)", |
| 158 | .help = "set integer to 3 or 4 (cmdmode option)", |
| 159 | .flags = PARSE_OPT_CMDMODE, |
| 160 | .callback = mode34_callback, |
| 161 | }, |
| 162 | OPT_CALLBACK('L', "length", &integer, "str", |
| 163 | "get length of <str>", length_callback), |
| 164 | OPT_FILENAME('F', "file", &file, "set file to <file>"), |
| 165 | OPT_GROUP("String options"), |
| 166 | OPT_STRING('s', "string", &string, "string", "get a string"), |
| 167 | OPT_STRING(0, "string2", &string, "str", "get another string"), |
| 168 | OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), |
| 169 | OPT_STRING('o', NULL, &string, "str", "get another string"), |
| 170 | OPT_NOOP_NOARG(0, "obsolete"), |
| 171 | OPT_SET_INT_F(0, "longhelp", &integer, "help text of this entry\n" |
| 172 | "spans multiple lines", 0, PARSE_OPT_NONEG), |
| 173 | OPT_STRING_LIST(0, "list", &list, "str", "add str to list"), |
| 174 | OPT_GROUP("Magic arguments"), |
| 175 | OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", |
| 176 | number_callback), |
| 177 | { |
| 178 | .type = OPTION_COUNTUP, |
| 179 | .short_name = '+', |
| 180 | .value = &boolean, |
| 181 | .precision = sizeof(boolean), |
| 182 | .help = "same as -b", |
| 183 | .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, |
| 184 | }, |
| 185 | { |
| 186 | .type = OPTION_COUNTUP, |
| 187 | .long_name = "ambiguous", |
| 188 | .value = &ambiguous, |
| 189 | .precision = sizeof(ambiguous), |
| 190 | .help = "positive ambiguity", |
| 191 | .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, |
| 192 | }, |
| 193 | { |
| 194 | .type = OPTION_COUNTUP, |
| 195 | .long_name = "no-ambiguous", |
| 196 | .value = &ambiguous, |
| 197 | .precision = sizeof(ambiguous), |
| 198 | .help = "negative ambiguity", |
| 199 | .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, |
| 200 | }, |
| 201 | OPT_GROUP("Standard options"), |
| 202 | OPT__ABBREV(&abbrev), |
| 203 | OPT__VERBOSE(&verbose, "be verbose"), |
| 204 | OPT__DRY_RUN(&dry_run, "dry run"), |
| 205 | OPT__QUIET(&quiet, "be quiet"), |
| 206 | OPT_CALLBACK(0, "expect", &expect, "string", |
| 207 | "expected output in the variable dump", |
| 208 | collect_expect), |
| 209 | OPT_GROUP("Alias"), |
| 210 | OPT_STRING('A', "alias-source", &string, "string", "get a string"), |
| 211 | OPT_ALIAS('Z', "alias-target", "alias-source"), |
| 212 | OPT_HIDDEN_GROUP("Hidden options"), |
| 213 | OPT_HIDDEN_BOOL(0, "hidden-bool", &boolean, "get a boolean"), |
| 214 | OPT_INTEGER_F('k', "hidden-integer", &integer, "get a integer", |
| 215 | PARSE_OPT_HIDDEN), |
| 216 | OPT_END(), |
| 217 | }; |
| 218 | int ret = 0; |
| 219 | |
| 220 | trace2_cmd_name("_parse_"); |
| 221 | |
| 222 | argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); |
| 223 | |
| 224 | if (length_cb.called) { |
| 225 | const char *arg = length_cb.arg; |
| 226 | int unset = length_cb.unset; |
| 227 | show(&expect, &ret, "Callback: \"%s\", %d", |
| 228 | (arg ? arg : "not set"), unset); |
| 229 | } |
| 230 | show(&expect, &ret, "boolean: %d", boolean); |
| 231 | show(&expect, &ret, "integer: %d", integer); |
| 232 | show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16); |
| 233 | show(&expect, &ret, "unsigned: %lu", unsigned_integer); |
| 234 | show(&expect, &ret, "u16: %"PRIuMAX, (uintmax_t) u16); |
| 235 | show(&expect, &ret, "timestamp: %"PRItime, timestamp); |
| 236 | show(&expect, &ret, "string: %s", string ? string : "(not set)"); |
| 237 | show(&expect, &ret, "abbrev: %d", abbrev); |
| 238 | show(&expect, &ret, "verbose: %d", verbose); |
| 239 | show(&expect, &ret, "quiet: %d", quiet); |
| 240 | show(&expect, &ret, "dry run: %s", dry_run ? "yes" : "no"); |
| 241 | show(&expect, &ret, "file: %s", file ? file : "(not set)"); |
| 242 | |
| 243 | for (size_t i = 0; i < list.nr; i++) |
| 244 | show(&expect, &ret, "list: %s", list.items[i].string); |
| 245 | |
| 246 | for (int i = 0; i < argc; i++) |
| 247 | show(&expect, &ret, "arg %02d: %s", i, argv[i]); |
| 248 | |
| 249 | expect.strdup_strings = 1; |
| 250 | string_list_clear(&expect, 0); |
| 251 | string_list_clear(&list, 0); |
| 252 | free(file); |
| 253 | |
| 254 | return ret; |
| 255 | } |
| 256 | |
| 257 | static void print_args(int argc, const char **argv) |
| 258 | { |
| 259 | int i; |
| 260 | for (i = 0; i < argc; i++) |
| 261 | printf("arg %02d: %s\n", i, argv[i]); |
| 262 | } |
| 263 | |
| 264 | static int parse_options_flags__cmd(int argc, const char **argv, |
| 265 | enum parse_opt_flags test_flags) |
| 266 | { |
| 267 | const char *usage[] = { |
| 268 | "<...> cmd [options]", |
| 269 | NULL |
| 270 | }; |
| 271 | int opt = 0; |
| 272 | const struct option options[] = { |
| 273 | OPT_INTEGER('o', "opt", &opt, "an integer option"), |
| 274 | OPT_END() |
| 275 | }; |
| 276 | |
| 277 | argc = parse_options(argc, argv, NULL, options, usage, test_flags); |
| 278 | |
| 279 | printf("opt: %d\n", opt); |
| 280 | print_args(argc, argv); |
| 281 | |
| 282 | return 0; |
| 283 | } |
| 284 | |
| 285 | static enum parse_opt_flags test_flags = 0; |
| 286 | static const struct option test_flag_options[] = { |
| 287 | OPT_GROUP("flag-options:"), |
| 288 | OPT_BIT(0, "keep-dashdash", &test_flags, |
| 289 | "pass PARSE_OPT_KEEP_DASHDASH to parse_options()", |
| 290 | PARSE_OPT_KEEP_DASHDASH), |
| 291 | OPT_BIT(0, "stop-at-non-option", &test_flags, |
| 292 | "pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()", |
| 293 | PARSE_OPT_STOP_AT_NON_OPTION), |
| 294 | OPT_BIT(0, "keep-argv0", &test_flags, |
| 295 | "pass PARSE_OPT_KEEP_ARGV0 to parse_options()", |
| 296 | PARSE_OPT_KEEP_ARGV0), |
| 297 | OPT_BIT(0, "keep-unknown-opt", &test_flags, |
| 298 | "pass PARSE_OPT_KEEP_UNKNOWN_OPT to parse_options()", |
| 299 | PARSE_OPT_KEEP_UNKNOWN_OPT), |
| 300 | OPT_BIT(0, "no-internal-help", &test_flags, |
| 301 | "pass PARSE_OPT_NO_INTERNAL_HELP to parse_options()", |
| 302 | PARSE_OPT_NO_INTERNAL_HELP), |
| 303 | OPT_BIT(0, "subcommand-optional", &test_flags, |
| 304 | "pass PARSE_OPT_SUBCOMMAND_OPTIONAL to parse_options()", |
| 305 | PARSE_OPT_SUBCOMMAND_OPTIONAL), |
| 306 | OPT_END() |
| 307 | }; |
| 308 | |
| 309 | int cmd__parse_options_flags(int argc, const char **argv) |
| 310 | { |
| 311 | const char *usage[] = { |
| 312 | "test-tool parse-options-flags [flag-options] cmd [options]", |
| 313 | NULL |
| 314 | }; |
| 315 | |
| 316 | argc = parse_options(argc, argv, NULL, test_flag_options, usage, |
| 317 | PARSE_OPT_STOP_AT_NON_OPTION); |
| 318 | |
| 319 | if (!argc || strcmp(argv[0], "cmd")) { |
| 320 | error("'cmd' is mandatory"); |
| 321 | usage_with_options(usage, test_flag_options); |
| 322 | } |
| 323 | |
| 324 | return parse_options_flags__cmd(argc, argv, test_flags); |
| 325 | } |
| 326 | |
| 327 | static int subcmd_one(int argc, const char **argv, const char *prefix UNUSED, |
| 328 | struct repository *repo UNUSED) |
| 329 | { |
| 330 | printf("fn: subcmd_one\n"); |
| 331 | print_args(argc, argv); |
| 332 | return 0; |
| 333 | } |
| 334 | |
| 335 | static int subcmd_two(int argc, const char **argv, const char *prefix UNUSED, |
| 336 | struct repository *repo UNUSED) |
| 337 | { |
| 338 | printf("fn: subcmd_two\n"); |
| 339 | print_args(argc, argv); |
| 340 | return 0; |
| 341 | } |
| 342 | |
| 343 | static int parse_subcommand__cmd(int argc, const char **argv, |
| 344 | enum parse_opt_flags test_flags) |
| 345 | { |
| 346 | const char *usage[] = { |
| 347 | "<...> cmd subcmd-one", |
| 348 | "<...> cmd subcmd-two", |
| 349 | NULL |
| 350 | }; |
| 351 | parse_opt_subcommand_fn *fn = NULL; |
| 352 | int opt = 0; |
| 353 | struct option options[] = { |
| 354 | OPT_SUBCOMMAND("subcmd-one", &fn, subcmd_one), |
| 355 | OPT_SUBCOMMAND("subcmd-two", &fn, subcmd_two), |
| 356 | OPT_INTEGER('o', "opt", &opt, "an integer option"), |
| 357 | OPT_END() |
| 358 | }; |
| 359 | |
| 360 | if (test_flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) |
| 361 | fn = subcmd_one; |
| 362 | argc = parse_options(argc, argv, NULL, options, usage, test_flags); |
| 363 | |
| 364 | printf("opt: %d\n", opt); |
| 365 | |
| 366 | return fn(argc, argv, NULL, NULL); |
| 367 | } |
| 368 | |
| 369 | int cmd__parse_subcommand(int argc, const char **argv) |
| 370 | { |
| 371 | const char *usage[] = { |
| 372 | "test-tool parse-subcommand [flag-options] cmd <subcommand>", |
| 373 | NULL |
| 374 | }; |
| 375 | |
| 376 | argc = parse_options(argc, argv, NULL, test_flag_options, usage, |
| 377 | PARSE_OPT_STOP_AT_NON_OPTION); |
| 378 | |
| 379 | if (!argc || strcmp(argv[0], "cmd")) { |
| 380 | error("'cmd' is mandatory"); |
| 381 | usage_with_options(usage, test_flag_options); |
| 382 | } |
| 383 | |
| 384 | return parse_subcommand__cmd(argc, argv, test_flags); |
| 385 | } |