js/src/shell/jsoptparse.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "shell/jsoptparse.h"
     9 #include <ctype.h>
    10 #include <stdarg.h>
    12 #include "jsutil.h"
    14 using namespace js;
    15 using namespace js::cli;
    16 using namespace js::cli::detail;
    18 const char OptionParser::prognameMeta[] = "{progname}";
    20 #define OPTION_CONVERT_IMPL(__cls) \
    21     bool \
    22     Option::is##__cls##Option() const \
    23     { \
    24         return kind == OptionKind##__cls; \
    25     } \
    26     __cls##Option * \
    27     Option::as##__cls##Option() \
    28     { \
    29         JS_ASSERT(is##__cls##Option()); \
    30         return static_cast<__cls##Option *>(this); \
    31     } \
    32     const __cls##Option * \
    33     Option::as##__cls##Option() const \
    34     { \
    35         return const_cast<Option *>(this)->as##__cls##Option(); \
    36     }
    38 ValuedOption *
    39 Option::asValued()
    40 {
    41     JS_ASSERT(isValued());
    42     return static_cast<ValuedOption *>(this);
    43 }
    45 const ValuedOption *
    46 Option::asValued() const
    47 {
    48     return const_cast<Option *>(this)->asValued();
    49 }
    51 OPTION_CONVERT_IMPL(Bool)
    52 OPTION_CONVERT_IMPL(String)
    53 OPTION_CONVERT_IMPL(Int)
    54 OPTION_CONVERT_IMPL(MultiString)
    56 void
    57 OptionParser::setArgTerminatesOptions(const char *name, bool enabled)
    58 {
    59     findArgument(name)->setTerminatesOptions(enabled);
    60 }
    62 void
    63 OptionParser::setArgCapturesRest(const char *name)
    64 {
    65     MOZ_ASSERT(restArgument == -1, "only one argument may be set to capture the rest");
    66     restArgument = findArgumentIndex(name);
    67     MOZ_ASSERT(restArgument != -1, "unknown argument name passed to setArgCapturesRest");
    68 }
    70 OptionParser::Result
    71 OptionParser::error(const char *fmt, ...)
    72 {
    73     va_list args;
    74     va_start(args, fmt);
    75     fprintf(stderr, "Error: ");
    76     vfprintf(stderr, fmt, args);
    77     va_end(args);
    78     fputs("\n\n", stderr);
    79     return ParseError;
    80 }
    82 /* Quick and dirty paragraph printer. */
    83 static void
    84 PrintParagraph(const char *text, unsigned startColno, const unsigned limitColno, bool padFirstLine)
    85 {
    86     unsigned colno = startColno;
    87     const char *it = text;
    89     if (padFirstLine)
    90         printf("%*s", startColno, "");
    92     while (*it != '\0') {
    93         JS_ASSERT(!isspace(*it));
    95         /* Delimit the current token. */
    96         const char *limit = it;
    97         while (!isspace(*limit) && *limit != '\0')
    98             ++limit;
   100         /*
   101          * If the current token is longer than the available number of columns,
   102          * then make a line break before printing the token.
   103          */
   104         JS_ASSERT(limit - it > 0);
   105         size_t tokLen = limit - it;
   106         JS_ASSERT(tokLen);
   107         if (tokLen + colno >= limitColno) {
   108             printf("\n%*s%.*s", startColno, "", int(tokLen), it);
   109             colno = startColno + tokLen;
   110         } else {
   111             printf("%.*s", int(tokLen), it);
   112             colno += tokLen;
   113         }
   115         switch (*limit) {
   116           case '\0':
   117             return;
   118           case ' ':
   119             putchar(' ');
   120             colno += 1;
   121             it = limit;
   122             while (*it == ' ')
   123                 ++it;
   124             break;
   125           case '\n':
   126             /* |text| wants to force a newline here. */
   127             printf("\n%*s", startColno, "");
   128             colno = startColno;
   129             it = limit + 1;
   130             /* Could also have line-leading spaces. */
   131             while (*it == ' ') {
   132                 putchar(' ');
   133                 ++colno;
   134                 ++it;
   135             }
   136             break;
   137           default:
   138             MOZ_ASSUME_UNREACHABLE("unhandled token splitting character in text");
   139         }
   140     }
   141 }
   143 static const char *
   144 OptionFlagsToFormatInfo(char shortflag, bool isValued, size_t *length)
   145 {
   146     static const char * const fmt[4] = { "  -%c --%s ",
   147                                          "  --%s ",
   148                                          "  -%c --%s=%s ",
   149                                          "  --%s=%s " };
   151     /* How mny chars w/o longflag? */
   152     size_t lengths[4] = { strlen(fmt[0]) - 3,
   153                           strlen(fmt[1]) - 3,
   154                           strlen(fmt[2]) - 5,
   155                           strlen(fmt[3]) - 5 };
   156     int index = isValued ? 2 : 0;
   157     if (!shortflag)
   158         index++;
   160     *length = lengths[index];
   161     return fmt[index];
   162 }
   164 OptionParser::Result
   165 OptionParser::printHelp(const char *progname)
   166 {
   167     const char *prefixEnd = strstr(usage, prognameMeta);
   168     if (prefixEnd) {
   169         printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
   170                prefixEnd + sizeof(prognameMeta) - 1);
   171     } else {
   172         puts(usage);
   173     }
   175     if (descr) {
   176         putchar('\n');
   177         PrintParagraph(descr, 2, descrWidth, true);
   178         putchar('\n');
   179     }
   181     if (ver)
   182         printf("\nVersion: %s\n\n", ver);
   184     if (!arguments.empty()) {
   185         printf("Arguments:\n");
   187         static const char fmt[] = "  %s ";
   188         size_t fmtChars = sizeof(fmt) - 2;
   189         size_t lhsLen = 0;
   190         for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it)
   191             lhsLen = Max(lhsLen, strlen((*it)->longflag) + fmtChars);
   193         for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) {
   194             Option *arg = *it;
   195             size_t chars = printf(fmt, arg->longflag);
   196             for (; chars < lhsLen; ++chars)
   197                 putchar(' ');
   198             PrintParagraph(arg->help, lhsLen, helpWidth, false);
   199             putchar('\n');
   200         }
   201         putchar('\n');
   202     }
   204     if (!options.empty()) {
   205         printf("Options:\n");
   207         /* Calculate sizes for column alignment. */
   208         size_t lhsLen = 0;
   209         for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
   210             Option *opt = *it;
   211             size_t longflagLen = strlen(opt->longflag);
   213             size_t fmtLen;
   214             OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
   216             size_t len = fmtLen + longflagLen;
   217             if (opt->isValued())
   218                 len += strlen(opt->asValued()->metavar);
   219             lhsLen = Max(lhsLen, len);
   220         }
   222         /* Print option help text. */
   223         for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
   224             Option *opt = *it;
   225             size_t fmtLen;
   226             const char *fmt = OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
   227             size_t chars;
   228             if (opt->isValued()) {
   229                 if (opt->shortflag)
   230                     chars = printf(fmt, opt->shortflag, opt->longflag, opt->asValued()->metavar);
   231                 else
   232                     chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
   233             } else {
   234                 if (opt->shortflag)
   235                     chars = printf(fmt, opt->shortflag, opt->longflag);
   236                 else
   237                     chars = printf(fmt, opt->longflag);
   238             }
   239             for (; chars < lhsLen; ++chars)
   240                 putchar(' ');
   241             PrintParagraph(opt->help, lhsLen, helpWidth, false);
   242             putchar('\n');
   243         }
   244     }
   246     return ParseHelp;
   247 }
   249 OptionParser::Result
   250 OptionParser::extractValue(size_t argc, char **argv, size_t *i, char **value)
   251 {
   252     JS_ASSERT(*i < argc);
   253     char *eq = strchr(argv[*i], '=');
   254     if (eq) {
   255         *value = eq + 1;
   256         if (*value[0] == '\0')
   257             return error("A value is required for option %.*s", eq - argv[*i], argv[*i]);
   258         return Okay;
   259     }
   261     if (argc == *i + 1)
   262         return error("Expected a value for option %s", argv[*i]);
   264     *i += 1;
   265     *value = argv[*i];
   266     return Okay;
   267 }
   269 OptionParser::Result
   270 OptionParser::handleOption(Option *opt, size_t argc, char **argv, size_t *i, bool *optionsAllowed)
   271 {
   272     if (opt->getTerminatesOptions())
   273         *optionsAllowed = false;
   275     switch (opt->kind) {
   276       case OptionKindBool:
   277       {
   278         if (opt == &helpOption)
   279             return printHelp(argv[0]);
   280         opt->asBoolOption()->value = true;
   281         return Okay;
   282       }
   283       /*
   284        * Valued options are allowed to specify their values either via
   285        * successive arguments or a single --longflag=value argument.
   286        */
   287       case OptionKindString:
   288       {
   289         char *value = nullptr;
   290         if (Result r = extractValue(argc, argv, i, &value))
   291             return r;
   292         opt->asStringOption()->value = value;
   293         return Okay;
   294       }
   295       case OptionKindInt:
   296       {
   297         char *value = nullptr;
   298         if (Result r = extractValue(argc, argv, i, &value))
   299             return r;
   300         opt->asIntOption()->value = atoi(value);
   301         return Okay;
   302       }
   303       case OptionKindMultiString:
   304       {
   305         char *value = nullptr;
   306         if (Result r = extractValue(argc, argv, i, &value))
   307             return r;
   308         StringArg arg(value, *i);
   309         return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
   310       }
   311       default:
   312         MOZ_ASSUME_UNREACHABLE("unhandled option kind");
   313     }
   314 }
   316 OptionParser::Result
   317 OptionParser::handleArg(size_t argc, char **argv, size_t *i, bool *optionsAllowed)
   318 {
   319     if (nextArgument >= arguments.length())
   320         return error("Too many arguments provided");
   322     Option *arg = arguments[nextArgument];
   324     if (arg->getTerminatesOptions())
   325         *optionsAllowed = false;
   327     switch (arg->kind) {
   328       case OptionKindString:
   329         arg->asStringOption()->value = argv[*i];
   330         nextArgument += 1;
   331         return Okay;
   332       case OptionKindMultiString:
   333       {
   334         /* Don't advance the next argument -- there can only be one (final) variadic argument. */
   335         StringArg value(argv[*i], *i);
   336         return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
   337       }
   338       default:
   339         MOZ_ASSUME_UNREACHABLE("unhandled argument kind");
   340     }
   341 }
   343 OptionParser::Result
   344 OptionParser::parseArgs(int inputArgc, char **argv)
   345 {
   346     JS_ASSERT(inputArgc >= 0);
   347     size_t argc = inputArgc;
   348     /* Permit a "no more options" capability, like |--| offers in many shell interfaces. */
   349     bool optionsAllowed = true;
   351     for (size_t i = 1; i < argc; ++i) {
   352         char *arg = argv[i];
   353         Result r;
   354         /* Note: solo dash option is actually a 'stdin' argument. */
   355         if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
   356             /* Option. */
   357             Option *opt;
   358             if (arg[1] == '-') {
   359                 if (arg[2] == '\0') {
   360                     /* End of options */
   361                     optionsAllowed = false;
   362                     nextArgument = restArgument;
   363                     continue;
   364                 } else {
   365                     /* Long option. */
   366                     opt = findOption(arg + 2);
   367                     if (!opt)
   368                         return error("Invalid long option: %s", arg);
   369                 }
   370             } else {
   371                 /* Short option */
   372                 if (arg[2] != '\0')
   373                     return error("Short option followed by junk: %s", arg);
   374                 opt = findOption(arg[1]);
   375                 if (!opt)
   376                     return error("Invalid short option: %s", arg);
   377             }
   379             r = handleOption(opt, argc, argv, &i, &optionsAllowed);
   380         } else {
   381             /* Argument. */
   382             r = handleArg(argc, argv, &i, &optionsAllowed);
   383         }
   385         if (r != Okay)
   386             return r;
   387     }
   388     return Okay;
   389 }
   391 void
   392 OptionParser::setHelpOption(char shortflag, const char *longflag, const char *help)
   393 {
   394     helpOption.setFlagInfo(shortflag, longflag, help);
   395 }
   397 bool
   398 OptionParser::getHelpOption() const
   399 {
   400     return helpOption.value;
   401 }
   403 bool
   404 OptionParser::getBoolOption(char shortflag) const
   405 {
   406     return findOption(shortflag)->asBoolOption()->value;
   407 }
   409 int
   410 OptionParser::getIntOption(char shortflag) const
   411 {
   412     return findOption(shortflag)->asIntOption()->value;
   413 }
   415 const char *
   416 OptionParser::getStringOption(char shortflag) const
   417 {
   418     return findOption(shortflag)->asStringOption()->value;
   419 }
   421 MultiStringRange
   422 OptionParser::getMultiStringOption(char shortflag) const
   423 {
   424     const MultiStringOption *mso = findOption(shortflag)->asMultiStringOption();
   425     return MultiStringRange(mso->strings.begin(), mso->strings.end());
   426 }
   428 bool
   429 OptionParser::getBoolOption(const char *longflag) const
   430 {
   431     return findOption(longflag)->asBoolOption()->value;
   432 }
   434 int
   435 OptionParser::getIntOption(const char *longflag) const
   436 {
   437     return findOption(longflag)->asIntOption()->value;
   438 }
   440 const char *
   441 OptionParser::getStringOption(const char *longflag) const
   442 {
   443     return findOption(longflag)->asStringOption()->value;
   444 }
   446 MultiStringRange
   447 OptionParser::getMultiStringOption(const char *longflag) const
   448 {
   449     const MultiStringOption *mso = findOption(longflag)->asMultiStringOption();
   450     return MultiStringRange(mso->strings.begin(), mso->strings.end());
   451 }
   453 OptionParser::~OptionParser()
   454 {
   455     for (Option **it = options.begin(), **end = options.end(); it != end; ++it)
   456         js_delete<Option>(*it);
   457     for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it)
   458         js_delete<Option>(*it);
   459 }
   461 Option *
   462 OptionParser::findOption(char shortflag)
   463 {
   464     for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
   465         if ((*it)->shortflag == shortflag)
   466             return *it;
   467     }
   469     return helpOption.shortflag == shortflag ? &helpOption : nullptr;
   470 }
   472 const Option *
   473 OptionParser::findOption(char shortflag) const
   474 {
   475     return const_cast<OptionParser *>(this)->findOption(shortflag);
   476 }
   478 Option *
   479 OptionParser::findOption(const char *longflag)
   480 {
   481     for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
   482         const char *target = (*it)->longflag;
   483         if ((*it)->isValued()) {
   484             size_t targetLen = strlen(target);
   485             /* Permit a trailing equals sign on the longflag argument. */
   486             for (size_t i = 0; i < targetLen; ++i) {
   487                 if (longflag[i] == '\0' || longflag[i] != target[i])
   488                     goto no_match;
   489             }
   490             if (longflag[targetLen] == '\0' || longflag[targetLen] == '=')
   491                 return *it;
   492         } else {
   493             if (strcmp(target, longflag) == 0)
   494                 return *it;
   495         }
   496   no_match:;
   497     }
   499     return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
   500 }
   502 const Option *
   503 OptionParser::findOption(const char *longflag) const
   504 {
   505     return const_cast<OptionParser *>(this)->findOption(longflag);
   506 }
   508 /* Argument accessors */
   510 int
   511 OptionParser::findArgumentIndex(const char *name) const
   512 {
   513     for (Option * const *it = arguments.begin(); it != arguments.end(); ++it) {
   514         const char *target = (*it)->longflag;
   515         if (strcmp(target, name) == 0)
   516             return it - arguments.begin();
   517     }
   518     return -1;
   519 }
   521 Option *
   522 OptionParser::findArgument(const char *name)
   523 {
   524     int index = findArgumentIndex(name);
   525     return (index == -1) ? nullptr : arguments[index];
   526 }
   528 const Option *
   529 OptionParser::findArgument(const char *name) const
   530 {
   531     int index = findArgumentIndex(name);
   532     return (index == -1) ? nullptr : arguments[index];
   533 }
   535 const char *
   536 OptionParser::getStringArg(const char *name) const
   537 {
   538     return findArgument(name)->asStringOption()->value;
   539 }
   541 MultiStringRange
   542 OptionParser::getMultiStringArg(const char *name) const
   543 {
   544     const MultiStringOption *mso = findArgument(name)->asMultiStringOption();
   545     return MultiStringRange(mso->strings.begin(), mso->strings.end());
   546 }
   548 /* Option builders */
   550 bool
   551 OptionParser::addIntOption(char shortflag, const char *longflag, const char *metavar,
   552                            const char *help, int defaultValue)
   553 {
   554     if (!options.reserve(options.length() + 1))
   555         return false;
   556     IntOption *io = js_new<IntOption>(shortflag, longflag, help, metavar, defaultValue);
   557     if (!io)
   558         return false;
   559     options.infallibleAppend(io);
   560     return true;
   561 }
   563 bool
   564 OptionParser::addBoolOption(char shortflag, const char *longflag, const char *help)
   565 {
   566     if (!options.reserve(options.length() + 1))
   567         return false;
   568     BoolOption *bo = js_new<BoolOption>(shortflag, longflag, help);
   569     if (!bo)
   570         return false;
   571     options.infallibleAppend(bo);
   572     return true;
   573 }
   575 bool
   576 OptionParser::addStringOption(char shortflag, const char *longflag, const char *metavar,
   577                               const char *help)
   578 {
   579     if (!options.reserve(options.length() + 1))
   580         return false;
   581     StringOption *so = js_new<StringOption>(shortflag, longflag, help, metavar);
   582     if (!so)
   583         return false;
   584     options.infallibleAppend(so);
   585     return true;
   586 }
   588 bool
   589 OptionParser::addMultiStringOption(char shortflag, const char *longflag, const char *metavar,
   590                                    const char *help)
   591 {
   592     if (!options.reserve(options.length() + 1))
   593         return false;
   594     MultiStringOption *mso = js_new<MultiStringOption>(shortflag, longflag, help, metavar);
   595     if (!mso)
   596         return false;
   597     options.infallibleAppend(mso);
   598     return true;
   599 }
   601 /* Argument builders */
   603 bool
   604 OptionParser::addOptionalStringArg(const char *name, const char *help)
   605 {
   606     if (!arguments.reserve(arguments.length() + 1))
   607         return false;
   608     StringOption *so = js_new<StringOption>(1, name, help, (const char *) nullptr);
   609     if (!so)
   610         return false;
   611     arguments.infallibleAppend(so);
   612     return true;
   613 }
   615 bool
   616 OptionParser::addOptionalMultiStringArg(const char *name, const char *help)
   617 {
   618     JS_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
   619     if (!arguments.reserve(arguments.length() + 1))
   620         return false;
   621     MultiStringOption *mso = js_new<MultiStringOption>(1, name, help, (const char *) nullptr);
   622     if (!mso)
   623         return false;
   624     arguments.infallibleAppend(mso);
   625     return true;
   626 }

mercurial