1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/shell/jsoptparse.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,626 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "shell/jsoptparse.h" 1.11 + 1.12 +#include <ctype.h> 1.13 +#include <stdarg.h> 1.14 + 1.15 +#include "jsutil.h" 1.16 + 1.17 +using namespace js; 1.18 +using namespace js::cli; 1.19 +using namespace js::cli::detail; 1.20 + 1.21 +const char OptionParser::prognameMeta[] = "{progname}"; 1.22 + 1.23 +#define OPTION_CONVERT_IMPL(__cls) \ 1.24 + bool \ 1.25 + Option::is##__cls##Option() const \ 1.26 + { \ 1.27 + return kind == OptionKind##__cls; \ 1.28 + } \ 1.29 + __cls##Option * \ 1.30 + Option::as##__cls##Option() \ 1.31 + { \ 1.32 + JS_ASSERT(is##__cls##Option()); \ 1.33 + return static_cast<__cls##Option *>(this); \ 1.34 + } \ 1.35 + const __cls##Option * \ 1.36 + Option::as##__cls##Option() const \ 1.37 + { \ 1.38 + return const_cast<Option *>(this)->as##__cls##Option(); \ 1.39 + } 1.40 + 1.41 +ValuedOption * 1.42 +Option::asValued() 1.43 +{ 1.44 + JS_ASSERT(isValued()); 1.45 + return static_cast<ValuedOption *>(this); 1.46 +} 1.47 + 1.48 +const ValuedOption * 1.49 +Option::asValued() const 1.50 +{ 1.51 + return const_cast<Option *>(this)->asValued(); 1.52 +} 1.53 + 1.54 +OPTION_CONVERT_IMPL(Bool) 1.55 +OPTION_CONVERT_IMPL(String) 1.56 +OPTION_CONVERT_IMPL(Int) 1.57 +OPTION_CONVERT_IMPL(MultiString) 1.58 + 1.59 +void 1.60 +OptionParser::setArgTerminatesOptions(const char *name, bool enabled) 1.61 +{ 1.62 + findArgument(name)->setTerminatesOptions(enabled); 1.63 +} 1.64 + 1.65 +void 1.66 +OptionParser::setArgCapturesRest(const char *name) 1.67 +{ 1.68 + MOZ_ASSERT(restArgument == -1, "only one argument may be set to capture the rest"); 1.69 + restArgument = findArgumentIndex(name); 1.70 + MOZ_ASSERT(restArgument != -1, "unknown argument name passed to setArgCapturesRest"); 1.71 +} 1.72 + 1.73 +OptionParser::Result 1.74 +OptionParser::error(const char *fmt, ...) 1.75 +{ 1.76 + va_list args; 1.77 + va_start(args, fmt); 1.78 + fprintf(stderr, "Error: "); 1.79 + vfprintf(stderr, fmt, args); 1.80 + va_end(args); 1.81 + fputs("\n\n", stderr); 1.82 + return ParseError; 1.83 +} 1.84 + 1.85 +/* Quick and dirty paragraph printer. */ 1.86 +static void 1.87 +PrintParagraph(const char *text, unsigned startColno, const unsigned limitColno, bool padFirstLine) 1.88 +{ 1.89 + unsigned colno = startColno; 1.90 + const char *it = text; 1.91 + 1.92 + if (padFirstLine) 1.93 + printf("%*s", startColno, ""); 1.94 + 1.95 + while (*it != '\0') { 1.96 + JS_ASSERT(!isspace(*it)); 1.97 + 1.98 + /* Delimit the current token. */ 1.99 + const char *limit = it; 1.100 + while (!isspace(*limit) && *limit != '\0') 1.101 + ++limit; 1.102 + 1.103 + /* 1.104 + * If the current token is longer than the available number of columns, 1.105 + * then make a line break before printing the token. 1.106 + */ 1.107 + JS_ASSERT(limit - it > 0); 1.108 + size_t tokLen = limit - it; 1.109 + JS_ASSERT(tokLen); 1.110 + if (tokLen + colno >= limitColno) { 1.111 + printf("\n%*s%.*s", startColno, "", int(tokLen), it); 1.112 + colno = startColno + tokLen; 1.113 + } else { 1.114 + printf("%.*s", int(tokLen), it); 1.115 + colno += tokLen; 1.116 + } 1.117 + 1.118 + switch (*limit) { 1.119 + case '\0': 1.120 + return; 1.121 + case ' ': 1.122 + putchar(' '); 1.123 + colno += 1; 1.124 + it = limit; 1.125 + while (*it == ' ') 1.126 + ++it; 1.127 + break; 1.128 + case '\n': 1.129 + /* |text| wants to force a newline here. */ 1.130 + printf("\n%*s", startColno, ""); 1.131 + colno = startColno; 1.132 + it = limit + 1; 1.133 + /* Could also have line-leading spaces. */ 1.134 + while (*it == ' ') { 1.135 + putchar(' '); 1.136 + ++colno; 1.137 + ++it; 1.138 + } 1.139 + break; 1.140 + default: 1.141 + MOZ_ASSUME_UNREACHABLE("unhandled token splitting character in text"); 1.142 + } 1.143 + } 1.144 +} 1.145 + 1.146 +static const char * 1.147 +OptionFlagsToFormatInfo(char shortflag, bool isValued, size_t *length) 1.148 +{ 1.149 + static const char * const fmt[4] = { " -%c --%s ", 1.150 + " --%s ", 1.151 + " -%c --%s=%s ", 1.152 + " --%s=%s " }; 1.153 + 1.154 + /* How mny chars w/o longflag? */ 1.155 + size_t lengths[4] = { strlen(fmt[0]) - 3, 1.156 + strlen(fmt[1]) - 3, 1.157 + strlen(fmt[2]) - 5, 1.158 + strlen(fmt[3]) - 5 }; 1.159 + int index = isValued ? 2 : 0; 1.160 + if (!shortflag) 1.161 + index++; 1.162 + 1.163 + *length = lengths[index]; 1.164 + return fmt[index]; 1.165 +} 1.166 + 1.167 +OptionParser::Result 1.168 +OptionParser::printHelp(const char *progname) 1.169 +{ 1.170 + const char *prefixEnd = strstr(usage, prognameMeta); 1.171 + if (prefixEnd) { 1.172 + printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname, 1.173 + prefixEnd + sizeof(prognameMeta) - 1); 1.174 + } else { 1.175 + puts(usage); 1.176 + } 1.177 + 1.178 + if (descr) { 1.179 + putchar('\n'); 1.180 + PrintParagraph(descr, 2, descrWidth, true); 1.181 + putchar('\n'); 1.182 + } 1.183 + 1.184 + if (ver) 1.185 + printf("\nVersion: %s\n\n", ver); 1.186 + 1.187 + if (!arguments.empty()) { 1.188 + printf("Arguments:\n"); 1.189 + 1.190 + static const char fmt[] = " %s "; 1.191 + size_t fmtChars = sizeof(fmt) - 2; 1.192 + size_t lhsLen = 0; 1.193 + for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) 1.194 + lhsLen = Max(lhsLen, strlen((*it)->longflag) + fmtChars); 1.195 + 1.196 + for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) { 1.197 + Option *arg = *it; 1.198 + size_t chars = printf(fmt, arg->longflag); 1.199 + for (; chars < lhsLen; ++chars) 1.200 + putchar(' '); 1.201 + PrintParagraph(arg->help, lhsLen, helpWidth, false); 1.202 + putchar('\n'); 1.203 + } 1.204 + putchar('\n'); 1.205 + } 1.206 + 1.207 + if (!options.empty()) { 1.208 + printf("Options:\n"); 1.209 + 1.210 + /* Calculate sizes for column alignment. */ 1.211 + size_t lhsLen = 0; 1.212 + for (Option **it = options.begin(), **end = options.end(); it != end; ++it) { 1.213 + Option *opt = *it; 1.214 + size_t longflagLen = strlen(opt->longflag); 1.215 + 1.216 + size_t fmtLen; 1.217 + OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen); 1.218 + 1.219 + size_t len = fmtLen + longflagLen; 1.220 + if (opt->isValued()) 1.221 + len += strlen(opt->asValued()->metavar); 1.222 + lhsLen = Max(lhsLen, len); 1.223 + } 1.224 + 1.225 + /* Print option help text. */ 1.226 + for (Option **it = options.begin(), **end = options.end(); it != end; ++it) { 1.227 + Option *opt = *it; 1.228 + size_t fmtLen; 1.229 + const char *fmt = OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen); 1.230 + size_t chars; 1.231 + if (opt->isValued()) { 1.232 + if (opt->shortflag) 1.233 + chars = printf(fmt, opt->shortflag, opt->longflag, opt->asValued()->metavar); 1.234 + else 1.235 + chars = printf(fmt, opt->longflag, opt->asValued()->metavar); 1.236 + } else { 1.237 + if (opt->shortflag) 1.238 + chars = printf(fmt, opt->shortflag, opt->longflag); 1.239 + else 1.240 + chars = printf(fmt, opt->longflag); 1.241 + } 1.242 + for (; chars < lhsLen; ++chars) 1.243 + putchar(' '); 1.244 + PrintParagraph(opt->help, lhsLen, helpWidth, false); 1.245 + putchar('\n'); 1.246 + } 1.247 + } 1.248 + 1.249 + return ParseHelp; 1.250 +} 1.251 + 1.252 +OptionParser::Result 1.253 +OptionParser::extractValue(size_t argc, char **argv, size_t *i, char **value) 1.254 +{ 1.255 + JS_ASSERT(*i < argc); 1.256 + char *eq = strchr(argv[*i], '='); 1.257 + if (eq) { 1.258 + *value = eq + 1; 1.259 + if (*value[0] == '\0') 1.260 + return error("A value is required for option %.*s", eq - argv[*i], argv[*i]); 1.261 + return Okay; 1.262 + } 1.263 + 1.264 + if (argc == *i + 1) 1.265 + return error("Expected a value for option %s", argv[*i]); 1.266 + 1.267 + *i += 1; 1.268 + *value = argv[*i]; 1.269 + return Okay; 1.270 +} 1.271 + 1.272 +OptionParser::Result 1.273 +OptionParser::handleOption(Option *opt, size_t argc, char **argv, size_t *i, bool *optionsAllowed) 1.274 +{ 1.275 + if (opt->getTerminatesOptions()) 1.276 + *optionsAllowed = false; 1.277 + 1.278 + switch (opt->kind) { 1.279 + case OptionKindBool: 1.280 + { 1.281 + if (opt == &helpOption) 1.282 + return printHelp(argv[0]); 1.283 + opt->asBoolOption()->value = true; 1.284 + return Okay; 1.285 + } 1.286 + /* 1.287 + * Valued options are allowed to specify their values either via 1.288 + * successive arguments or a single --longflag=value argument. 1.289 + */ 1.290 + case OptionKindString: 1.291 + { 1.292 + char *value = nullptr; 1.293 + if (Result r = extractValue(argc, argv, i, &value)) 1.294 + return r; 1.295 + opt->asStringOption()->value = value; 1.296 + return Okay; 1.297 + } 1.298 + case OptionKindInt: 1.299 + { 1.300 + char *value = nullptr; 1.301 + if (Result r = extractValue(argc, argv, i, &value)) 1.302 + return r; 1.303 + opt->asIntOption()->value = atoi(value); 1.304 + return Okay; 1.305 + } 1.306 + case OptionKindMultiString: 1.307 + { 1.308 + char *value = nullptr; 1.309 + if (Result r = extractValue(argc, argv, i, &value)) 1.310 + return r; 1.311 + StringArg arg(value, *i); 1.312 + return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail; 1.313 + } 1.314 + default: 1.315 + MOZ_ASSUME_UNREACHABLE("unhandled option kind"); 1.316 + } 1.317 +} 1.318 + 1.319 +OptionParser::Result 1.320 +OptionParser::handleArg(size_t argc, char **argv, size_t *i, bool *optionsAllowed) 1.321 +{ 1.322 + if (nextArgument >= arguments.length()) 1.323 + return error("Too many arguments provided"); 1.324 + 1.325 + Option *arg = arguments[nextArgument]; 1.326 + 1.327 + if (arg->getTerminatesOptions()) 1.328 + *optionsAllowed = false; 1.329 + 1.330 + switch (arg->kind) { 1.331 + case OptionKindString: 1.332 + arg->asStringOption()->value = argv[*i]; 1.333 + nextArgument += 1; 1.334 + return Okay; 1.335 + case OptionKindMultiString: 1.336 + { 1.337 + /* Don't advance the next argument -- there can only be one (final) variadic argument. */ 1.338 + StringArg value(argv[*i], *i); 1.339 + return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail; 1.340 + } 1.341 + default: 1.342 + MOZ_ASSUME_UNREACHABLE("unhandled argument kind"); 1.343 + } 1.344 +} 1.345 + 1.346 +OptionParser::Result 1.347 +OptionParser::parseArgs(int inputArgc, char **argv) 1.348 +{ 1.349 + JS_ASSERT(inputArgc >= 0); 1.350 + size_t argc = inputArgc; 1.351 + /* Permit a "no more options" capability, like |--| offers in many shell interfaces. */ 1.352 + bool optionsAllowed = true; 1.353 + 1.354 + for (size_t i = 1; i < argc; ++i) { 1.355 + char *arg = argv[i]; 1.356 + Result r; 1.357 + /* Note: solo dash option is actually a 'stdin' argument. */ 1.358 + if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) { 1.359 + /* Option. */ 1.360 + Option *opt; 1.361 + if (arg[1] == '-') { 1.362 + if (arg[2] == '\0') { 1.363 + /* End of options */ 1.364 + optionsAllowed = false; 1.365 + nextArgument = restArgument; 1.366 + continue; 1.367 + } else { 1.368 + /* Long option. */ 1.369 + opt = findOption(arg + 2); 1.370 + if (!opt) 1.371 + return error("Invalid long option: %s", arg); 1.372 + } 1.373 + } else { 1.374 + /* Short option */ 1.375 + if (arg[2] != '\0') 1.376 + return error("Short option followed by junk: %s", arg); 1.377 + opt = findOption(arg[1]); 1.378 + if (!opt) 1.379 + return error("Invalid short option: %s", arg); 1.380 + } 1.381 + 1.382 + r = handleOption(opt, argc, argv, &i, &optionsAllowed); 1.383 + } else { 1.384 + /* Argument. */ 1.385 + r = handleArg(argc, argv, &i, &optionsAllowed); 1.386 + } 1.387 + 1.388 + if (r != Okay) 1.389 + return r; 1.390 + } 1.391 + return Okay; 1.392 +} 1.393 + 1.394 +void 1.395 +OptionParser::setHelpOption(char shortflag, const char *longflag, const char *help) 1.396 +{ 1.397 + helpOption.setFlagInfo(shortflag, longflag, help); 1.398 +} 1.399 + 1.400 +bool 1.401 +OptionParser::getHelpOption() const 1.402 +{ 1.403 + return helpOption.value; 1.404 +} 1.405 + 1.406 +bool 1.407 +OptionParser::getBoolOption(char shortflag) const 1.408 +{ 1.409 + return findOption(shortflag)->asBoolOption()->value; 1.410 +} 1.411 + 1.412 +int 1.413 +OptionParser::getIntOption(char shortflag) const 1.414 +{ 1.415 + return findOption(shortflag)->asIntOption()->value; 1.416 +} 1.417 + 1.418 +const char * 1.419 +OptionParser::getStringOption(char shortflag) const 1.420 +{ 1.421 + return findOption(shortflag)->asStringOption()->value; 1.422 +} 1.423 + 1.424 +MultiStringRange 1.425 +OptionParser::getMultiStringOption(char shortflag) const 1.426 +{ 1.427 + const MultiStringOption *mso = findOption(shortflag)->asMultiStringOption(); 1.428 + return MultiStringRange(mso->strings.begin(), mso->strings.end()); 1.429 +} 1.430 + 1.431 +bool 1.432 +OptionParser::getBoolOption(const char *longflag) const 1.433 +{ 1.434 + return findOption(longflag)->asBoolOption()->value; 1.435 +} 1.436 + 1.437 +int 1.438 +OptionParser::getIntOption(const char *longflag) const 1.439 +{ 1.440 + return findOption(longflag)->asIntOption()->value; 1.441 +} 1.442 + 1.443 +const char * 1.444 +OptionParser::getStringOption(const char *longflag) const 1.445 +{ 1.446 + return findOption(longflag)->asStringOption()->value; 1.447 +} 1.448 + 1.449 +MultiStringRange 1.450 +OptionParser::getMultiStringOption(const char *longflag) const 1.451 +{ 1.452 + const MultiStringOption *mso = findOption(longflag)->asMultiStringOption(); 1.453 + return MultiStringRange(mso->strings.begin(), mso->strings.end()); 1.454 +} 1.455 + 1.456 +OptionParser::~OptionParser() 1.457 +{ 1.458 + for (Option **it = options.begin(), **end = options.end(); it != end; ++it) 1.459 + js_delete<Option>(*it); 1.460 + for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) 1.461 + js_delete<Option>(*it); 1.462 +} 1.463 + 1.464 +Option * 1.465 +OptionParser::findOption(char shortflag) 1.466 +{ 1.467 + for (Option **it = options.begin(), **end = options.end(); it != end; ++it) { 1.468 + if ((*it)->shortflag == shortflag) 1.469 + return *it; 1.470 + } 1.471 + 1.472 + return helpOption.shortflag == shortflag ? &helpOption : nullptr; 1.473 +} 1.474 + 1.475 +const Option * 1.476 +OptionParser::findOption(char shortflag) const 1.477 +{ 1.478 + return const_cast<OptionParser *>(this)->findOption(shortflag); 1.479 +} 1.480 + 1.481 +Option * 1.482 +OptionParser::findOption(const char *longflag) 1.483 +{ 1.484 + for (Option **it = options.begin(), **end = options.end(); it != end; ++it) { 1.485 + const char *target = (*it)->longflag; 1.486 + if ((*it)->isValued()) { 1.487 + size_t targetLen = strlen(target); 1.488 + /* Permit a trailing equals sign on the longflag argument. */ 1.489 + for (size_t i = 0; i < targetLen; ++i) { 1.490 + if (longflag[i] == '\0' || longflag[i] != target[i]) 1.491 + goto no_match; 1.492 + } 1.493 + if (longflag[targetLen] == '\0' || longflag[targetLen] == '=') 1.494 + return *it; 1.495 + } else { 1.496 + if (strcmp(target, longflag) == 0) 1.497 + return *it; 1.498 + } 1.499 + no_match:; 1.500 + } 1.501 + 1.502 + return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption; 1.503 +} 1.504 + 1.505 +const Option * 1.506 +OptionParser::findOption(const char *longflag) const 1.507 +{ 1.508 + return const_cast<OptionParser *>(this)->findOption(longflag); 1.509 +} 1.510 + 1.511 +/* Argument accessors */ 1.512 + 1.513 +int 1.514 +OptionParser::findArgumentIndex(const char *name) const 1.515 +{ 1.516 + for (Option * const *it = arguments.begin(); it != arguments.end(); ++it) { 1.517 + const char *target = (*it)->longflag; 1.518 + if (strcmp(target, name) == 0) 1.519 + return it - arguments.begin(); 1.520 + } 1.521 + return -1; 1.522 +} 1.523 + 1.524 +Option * 1.525 +OptionParser::findArgument(const char *name) 1.526 +{ 1.527 + int index = findArgumentIndex(name); 1.528 + return (index == -1) ? nullptr : arguments[index]; 1.529 +} 1.530 + 1.531 +const Option * 1.532 +OptionParser::findArgument(const char *name) const 1.533 +{ 1.534 + int index = findArgumentIndex(name); 1.535 + return (index == -1) ? nullptr : arguments[index]; 1.536 +} 1.537 + 1.538 +const char * 1.539 +OptionParser::getStringArg(const char *name) const 1.540 +{ 1.541 + return findArgument(name)->asStringOption()->value; 1.542 +} 1.543 + 1.544 +MultiStringRange 1.545 +OptionParser::getMultiStringArg(const char *name) const 1.546 +{ 1.547 + const MultiStringOption *mso = findArgument(name)->asMultiStringOption(); 1.548 + return MultiStringRange(mso->strings.begin(), mso->strings.end()); 1.549 +} 1.550 + 1.551 +/* Option builders */ 1.552 + 1.553 +bool 1.554 +OptionParser::addIntOption(char shortflag, const char *longflag, const char *metavar, 1.555 + const char *help, int defaultValue) 1.556 +{ 1.557 + if (!options.reserve(options.length() + 1)) 1.558 + return false; 1.559 + IntOption *io = js_new<IntOption>(shortflag, longflag, help, metavar, defaultValue); 1.560 + if (!io) 1.561 + return false; 1.562 + options.infallibleAppend(io); 1.563 + return true; 1.564 +} 1.565 + 1.566 +bool 1.567 +OptionParser::addBoolOption(char shortflag, const char *longflag, const char *help) 1.568 +{ 1.569 + if (!options.reserve(options.length() + 1)) 1.570 + return false; 1.571 + BoolOption *bo = js_new<BoolOption>(shortflag, longflag, help); 1.572 + if (!bo) 1.573 + return false; 1.574 + options.infallibleAppend(bo); 1.575 + return true; 1.576 +} 1.577 + 1.578 +bool 1.579 +OptionParser::addStringOption(char shortflag, const char *longflag, const char *metavar, 1.580 + const char *help) 1.581 +{ 1.582 + if (!options.reserve(options.length() + 1)) 1.583 + return false; 1.584 + StringOption *so = js_new<StringOption>(shortflag, longflag, help, metavar); 1.585 + if (!so) 1.586 + return false; 1.587 + options.infallibleAppend(so); 1.588 + return true; 1.589 +} 1.590 + 1.591 +bool 1.592 +OptionParser::addMultiStringOption(char shortflag, const char *longflag, const char *metavar, 1.593 + const char *help) 1.594 +{ 1.595 + if (!options.reserve(options.length() + 1)) 1.596 + return false; 1.597 + MultiStringOption *mso = js_new<MultiStringOption>(shortflag, longflag, help, metavar); 1.598 + if (!mso) 1.599 + return false; 1.600 + options.infallibleAppend(mso); 1.601 + return true; 1.602 +} 1.603 + 1.604 +/* Argument builders */ 1.605 + 1.606 +bool 1.607 +OptionParser::addOptionalStringArg(const char *name, const char *help) 1.608 +{ 1.609 + if (!arguments.reserve(arguments.length() + 1)) 1.610 + return false; 1.611 + StringOption *so = js_new<StringOption>(1, name, help, (const char *) nullptr); 1.612 + if (!so) 1.613 + return false; 1.614 + arguments.infallibleAppend(so); 1.615 + return true; 1.616 +} 1.617 + 1.618 +bool 1.619 +OptionParser::addOptionalMultiStringArg(const char *name, const char *help) 1.620 +{ 1.621 + JS_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic()); 1.622 + if (!arguments.reserve(arguments.length() + 1)) 1.623 + return false; 1.624 + MultiStringOption *mso = js_new<MultiStringOption>(1, name, help, (const char *) nullptr); 1.625 + if (!mso) 1.626 + return false; 1.627 + arguments.infallibleAppend(mso); 1.628 + return true; 1.629 +}