PageRenderTime 124ms CodeModel.GetById 22ms app.highlight 92ms RepoModel.GetById 1ms app.codeStats 0ms

/js/src/shell/jsoptparse.cpp

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