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 :
45 : using namespace js;
46 : using namespace js::cli;
47 : using namespace js::cli::detail;
48 :
49 : const 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 :
69 : ValuedOption *
70 0 : Option::asValued()
71 : {
72 0 : JS_ASSERT(isValued());
73 0 : return static_cast<ValuedOption *>(this);
74 : }
75 :
76 : const ValuedOption *
77 0 : Option::asValued() const
78 : {
79 0 : return const_cast<Option *>(this)->asValued();
80 : }
81 :
82 683592 : OPTION_CONVERT_IMPL(Bool)
83 107919 : OPTION_CONVERT_IMPL(String)
84 55215 : OPTION_CONVERT_IMPL(Int)
85 276075 : OPTION_CONVERT_IMPL(MultiString)
86 :
87 : void
88 18405 : OptionParser::setArgTerminatesOptions(const char *name, bool enabled)
89 : {
90 18405 : findArgument(name)->setTerminatesOptions(enabled);
91 18405 : }
92 :
93 : OptionParser::Result
94 0 : OptionParser::error(const char *fmt, ...)
95 : {
96 : va_list args;
97 0 : va_start(args, fmt);
98 0 : fprintf(stderr, "Error: ");
99 0 : vfprintf(stderr, fmt, args);
100 0 : va_end(args);
101 0 : fputs("\n\n", stderr);
102 0 : return ParseError;
103 : }
104 :
105 : /* Quick and dirty paragraph printer. */
106 : static void
107 0 : PrintParagraph(const char *text, unsigned startColno, const unsigned limitColno, bool padFirstLine)
108 : {
109 0 : unsigned colno = startColno;
110 0 : const char *it = text;
111 :
112 0 : if (padFirstLine)
113 0 : printf("%*s", startColno, "");
114 :
115 0 : while (*it != '\0') {
116 0 : JS_ASSERT(!isspace(*it));
117 :
118 : /* Delimit the current token. */
119 0 : const char *limit = it;
120 0 : while (!isspace(*limit) && *limit != '\0')
121 0 : ++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 0 : JS_ASSERT(limit - it > 0);
128 0 : size_t tokLen = limit - it;
129 0 : JS_ASSERT(tokLen);
130 0 : if (tokLen + colno >= limitColno) {
131 0 : printf("\n%*s%.*s", startColno, "", int(tokLen), it);
132 0 : colno = startColno + tokLen;
133 : } else {
134 0 : printf("%.*s", int(tokLen), it);
135 0 : colno += tokLen;
136 : }
137 :
138 0 : switch (*limit) {
139 : case '\0':
140 0 : return;
141 : case ' ':
142 0 : putchar(' ');
143 0 : colno += 1;
144 0 : it = limit;
145 0 : while (*it == ' ')
146 0 : ++it;
147 0 : break;
148 : case '\n':
149 : /* |text| wants to force a newline here. */
150 0 : printf("\n%*s", startColno, "");
151 0 : colno = startColno;
152 0 : it = limit + 1;
153 : /* Could also have line-leading spaces. */
154 0 : while (*it == ' ') {
155 0 : putchar(' ');
156 0 : ++colno;
157 0 : ++it;
158 : }
159 0 : break;
160 : default:
161 0 : JS_NOT_REACHED("unhandled token splitting character in text");
162 : }
163 : }
164 : }
165 :
166 : static const char *
167 0 : OptionFlagsToFormatInfo(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 0 : size_t lengths[4] = { strlen(fmt[0]) - 3,
176 0 : strlen(fmt[1]) - 3,
177 0 : strlen(fmt[2]) - 5,
178 0 : strlen(fmt[3]) - 5 };
179 0 : int index = isValued ? 2 : 0;
180 0 : if (!shortflag)
181 0 : index++;
182 :
183 0 : *length = lengths[index];
184 0 : return fmt[index];
185 : }
186 :
187 : OptionParser::Result
188 0 : OptionParser::printHelp(const char *progname)
189 : {
190 0 : const char *prefixEnd = strstr(usage, prognameMeta);
191 0 : if (prefixEnd) {
192 : printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
193 0 : prefixEnd + sizeof(prognameMeta) - 1);
194 : } else {
195 0 : puts(usage);
196 : }
197 :
198 0 : if (descr) {
199 0 : putchar('\n');
200 0 : PrintParagraph(descr, 2, descrWidth, true);
201 0 : putchar('\n');
202 : }
203 :
204 0 : if (ver)
205 0 : printf("\nVersion: %s\n\n", ver);
206 :
207 0 : if (!arguments.empty()) {
208 0 : printf("Arguments:\n");
209 :
210 : static const char fmt[] = " %s ";
211 0 : size_t fmtChars = sizeof(fmt) - 2;
212 0 : size_t lhsLen = 0;
213 0 : for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it)
214 0 : lhsLen = JS_MAX(lhsLen, strlen((*it)->longflag) + fmtChars);
215 :
216 0 : for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) {
217 0 : Option *arg = *it;
218 0 : size_t chars = printf(fmt, arg->longflag);
219 0 : for (; chars < lhsLen; ++chars)
220 0 : putchar(' ');
221 0 : PrintParagraph(arg->help, lhsLen, helpWidth, false);
222 0 : putchar('\n');
223 : }
224 0 : putchar('\n');
225 : }
226 :
227 0 : if (!options.empty()) {
228 0 : printf("Options:\n");
229 :
230 : /* Calculate sizes for column alignment. */
231 0 : size_t lhsLen = 0;
232 0 : for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
233 0 : Option *opt = *it;
234 0 : size_t longflagLen = strlen(opt->longflag);
235 :
236 : size_t fmtLen;
237 0 : OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
238 :
239 0 : size_t len = fmtLen + longflagLen;
240 0 : if (opt->isValued())
241 0 : len += strlen(opt->asValued()->metavar);
242 0 : lhsLen = JS_MAX(lhsLen, len);
243 : }
244 :
245 : /* Print option help text. */
246 0 : for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
247 0 : Option *opt = *it;
248 : size_t fmtLen;
249 0 : const char *fmt = OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
250 : size_t chars;
251 0 : if (opt->isValued()) {
252 0 : if (opt->shortflag)
253 0 : chars = printf(fmt, opt->shortflag, opt->longflag, opt->asValued()->metavar);
254 : else
255 0 : chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
256 : } else {
257 0 : if (opt->shortflag)
258 0 : chars = printf(fmt, opt->shortflag, opt->longflag);
259 : else
260 0 : chars = printf(fmt, opt->longflag);
261 : }
262 0 : for (; chars < lhsLen; ++chars)
263 0 : putchar(' ');
264 0 : PrintParagraph(opt->help, lhsLen, helpWidth, false);
265 0 : putchar('\n');
266 : }
267 : }
268 :
269 0 : return ParseHelp;
270 : }
271 :
272 : OptionParser::Result
273 55215 : OptionParser::extractValue(size_t argc, char **argv, size_t *i, char **value)
274 : {
275 55215 : JS_ASSERT(*i < argc);
276 55215 : char *eq = strchr(argv[*i], '=');
277 55215 : if (eq) {
278 0 : *value = eq + 1;
279 0 : if (value[0] == '\0')
280 0 : return error("A value is required for option %.*s", eq - argv[*i], argv[*i]);
281 0 : return Okay;
282 : }
283 :
284 55215 : if (argc == *i + 1)
285 0 : return error("Expected a value for option %s", argv[*i]);
286 :
287 55215 : *i += 1;
288 55215 : *value = argv[*i];
289 55215 : return Okay;
290 : }
291 :
292 : OptionParser::Result
293 94584 : OptionParser::handleOption(Option *opt, size_t argc, char **argv, size_t *i, bool *optionsAllowed)
294 : {
295 94584 : if (opt->getTerminatesOptions())
296 0 : *optionsAllowed = false;
297 :
298 94584 : switch (opt->kind) {
299 : case OptionKindBool:
300 : {
301 39369 : if (opt == &helpOption)
302 0 : return printHelp(argv[0]);
303 39369 : opt->asBoolOption()->value = true;
304 39369 : 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 0 : char *value = NULL;
313 0 : if (Result r = extractValue(argc, argv, i, &value))
314 0 : return r;
315 0 : opt->asStringOption()->value = value;
316 0 : return Okay;
317 : }
318 : case OptionKindInt:
319 : {
320 0 : char *value = NULL;
321 0 : if (Result r = extractValue(argc, argv, i, &value))
322 0 : return r;
323 0 : opt->asIntOption()->value = atoi(value);
324 0 : return Okay;
325 : }
326 : case OptionKindMultiString:
327 : {
328 55215 : char *value = NULL;
329 55215 : if (Result r = extractValue(argc, argv, i, &value))
330 0 : return r;
331 55215 : StringArg arg(value, *i);
332 55215 : return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
333 : }
334 : default:
335 0 : JS_NOT_REACHED("unhandled option kind");
336 : return Fail;
337 : }
338 : }
339 :
340 : OptionParser::Result
341 0 : OptionParser::handleArg(size_t argc, char **argv, size_t *i, bool *optionsAllowed)
342 : {
343 0 : if (nextArgument >= arguments.length())
344 0 : return error("Too many arguments provided");
345 :
346 0 : Option *arg = arguments[nextArgument];
347 :
348 0 : if (arg->getTerminatesOptions())
349 0 : *optionsAllowed = false;
350 :
351 0 : switch (arg->kind) {
352 : case OptionKindString:
353 0 : arg->asStringOption()->value = argv[*i];
354 0 : nextArgument += 1;
355 0 : return Okay;
356 : case OptionKindMultiString:
357 : {
358 : /* Don't advance the next argument -- there can only be one (final) variadic argument. */
359 0 : StringArg value(argv[*i], *i);
360 0 : return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
361 : }
362 : default:
363 0 : JS_NOT_REACHED("unhandled argument kind");
364 : return Fail;
365 : }
366 : }
367 :
368 : OptionParser::Result
369 18405 : OptionParser::parseArgs(int inputArgc, char **argv)
370 : {
371 18405 : JS_ASSERT(inputArgc >= 0);
372 18405 : size_t argc = inputArgc;
373 : /* Permit a "no more options" capability, like |--| offers in many shell interfaces. */
374 18405 : bool optionsAllowed = true;
375 :
376 225978 : for (size_t i = 1; i < argc; ++i) {
377 94584 : char *arg = argv[i];
378 : Result r;
379 : /* Note: solo dash option is actually a 'stdin' argument. */
380 94584 : if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
381 : /* Option. */
382 : Option *opt;
383 94584 : if (arg[1] == '-') {
384 : /* Long option. */
385 0 : opt = findOption(arg + 2);
386 0 : if (!opt)
387 0 : return error("Invalid long option: %s", arg);
388 : } else {
389 : /* Short option */
390 94584 : if (arg[2] != '\0')
391 0 : return error("Short option followed by junk: %s", arg);
392 94584 : opt = findOption(arg[1]);
393 94584 : if (!opt)
394 0 : return error("Invalid short option: %s", arg);
395 : }
396 :
397 94584 : r = handleOption(opt, argc, argv, &i, &optionsAllowed);
398 : } else {
399 : /* Argument. */
400 0 : r = handleArg(argc, argv, &i, &optionsAllowed);
401 : }
402 :
403 94584 : switch (r) {
404 : case Okay:
405 : break;
406 : default:
407 0 : return r;
408 : }
409 : }
410 18405 : return Okay;
411 : }
412 :
413 : void
414 0 : OptionParser::setHelpOption(char shortflag, const char *longflag, const char *help)
415 : {
416 0 : helpOption.setFlagInfo(shortflag, longflag, help);
417 0 : }
418 :
419 : bool
420 18405 : OptionParser::getHelpOption() const
421 : {
422 18405 : return helpOption.value;
423 : }
424 :
425 : bool
426 201618 : OptionParser::getBoolOption(char shortflag) const
427 : {
428 201618 : return findOption(shortflag)->asBoolOption()->value;
429 : }
430 :
431 : int
432 18405 : OptionParser::getIntOption(char shortflag) const
433 : {
434 18405 : return findOption(shortflag)->asIntOption()->value;
435 : }
436 :
437 : const char *
438 18405 : OptionParser::getStringOption(char shortflag) const
439 : {
440 18405 : return findOption(shortflag)->asStringOption()->value;
441 : }
442 :
443 : MultiStringRange
444 36810 : OptionParser::getMultiStringOption(char shortflag) const
445 : {
446 36810 : const MultiStringOption *mso = findOption(shortflag)->asMultiStringOption();
447 36810 : return MultiStringRange(mso->strings.begin(), mso->strings.end());
448 : }
449 :
450 : bool
451 0 : OptionParser::getBoolOption(const char *longflag) const
452 : {
453 0 : return findOption(longflag)->asBoolOption()->value;
454 : }
455 :
456 : int
457 0 : OptionParser::getIntOption(const char *longflag) const
458 : {
459 0 : return findOption(longflag)->asIntOption()->value;
460 : }
461 :
462 : const char *
463 0 : OptionParser::getStringOption(const char *longflag) const
464 : {
465 0 : return findOption(longflag)->asStringOption()->value;
466 : }
467 :
468 : MultiStringRange
469 0 : OptionParser::getMultiStringOption(const char *longflag) const
470 : {
471 0 : const MultiStringOption *mso = findOption(longflag)->asMultiStringOption();
472 0 : return MultiStringRange(mso->strings.begin(), mso->strings.end());
473 : }
474 :
475 36810 : OptionParser::~OptionParser()
476 : {
477 276075 : for (Option **it = options.begin(), **end = options.end(); it != end; ++it)
478 257670 : Foreground::delete_<Option>(*it);
479 55215 : for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it)
480 36810 : Foreground::delete_<Option>(*it);
481 18405 : }
482 :
483 : Option *
484 369822 : OptionParser::findOption(char shortflag)
485 : {
486 2389721 : for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
487 2389721 : if ((*it)->shortflag == shortflag)
488 369822 : return *it;
489 : }
490 :
491 0 : return helpOption.shortflag == shortflag ? &helpOption : NULL;
492 : }
493 :
494 : const Option *
495 275238 : OptionParser::findOption(char shortflag) const
496 : {
497 275238 : return const_cast<OptionParser *>(this)->findOption(shortflag);
498 : }
499 :
500 : Option *
501 0 : OptionParser::findOption(const char *longflag)
502 : {
503 0 : for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
504 0 : const char *target = (*it)->longflag;
505 0 : if ((*it)->isValued()) {
506 0 : size_t targetLen = strlen(target);
507 : /* Permit a trailing equals sign on the longflag argument. */
508 0 : for (size_t i = 0; i < targetLen; ++i) {
509 0 : if (longflag[i] == '\0' || longflag[i] != target[i])
510 : goto no_match;
511 : }
512 0 : if (longflag[targetLen] == '\0' || longflag[targetLen] == '=')
513 0 : return *it;
514 : } else {
515 0 : if (strcmp(target, longflag) == 0)
516 0 : return *it;
517 : }
518 : no_match:;
519 : }
520 :
521 0 : return strcmp(helpOption.longflag, longflag) ? NULL : &helpOption;
522 : }
523 :
524 : const Option *
525 0 : OptionParser::findOption(const char *longflag) const
526 : {
527 0 : return const_cast<OptionParser *>(this)->findOption(longflag);
528 : }
529 :
530 : /* Argument accessors */
531 :
532 : Option *
533 54378 : OptionParser::findArgument(const char *name)
534 : {
535 72783 : for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) {
536 72783 : const char *target = (*it)->longflag;
537 72783 : if (strcmp(target, name) == 0)
538 54378 : return *it;
539 : }
540 0 : return NULL;
541 : }
542 :
543 : const Option *
544 35973 : OptionParser::findArgument(const char *name) const
545 : {
546 35973 : return const_cast<OptionParser *>(this)->findArgument(name);
547 : }
548 :
549 : const char *
550 17568 : OptionParser::getStringArg(const char *name) const
551 : {
552 17568 : return findArgument(name)->asStringOption()->value;
553 : }
554 :
555 : MultiStringRange
556 18405 : OptionParser::getMultiStringArg(const char *name) const
557 : {
558 18405 : const MultiStringOption *mso = findArgument(name)->asMultiStringOption();
559 18405 : return MultiStringRange(mso->strings.begin(), mso->strings.end());
560 : }
561 :
562 : /* Option builders */
563 :
564 : bool
565 18405 : OptionParser::addIntOption(char shortflag, const char *longflag, const char *metavar,
566 : const char *help, int defaultValue)
567 : {
568 18405 : if (!options.reserve(options.length() + 1))
569 0 : return false;
570 : IntOption *io = OffTheBooks::new_<IntOption>(shortflag, longflag, help, metavar,
571 18405 : defaultValue);
572 18405 : if (!io)
573 0 : return false;
574 18405 : options.infallibleAppend(io);
575 18405 : return true;
576 : }
577 :
578 : bool
579 184050 : OptionParser::addBoolOption(char shortflag, const char *longflag, const char *help)
580 : {
581 184050 : if (!options.reserve(options.length() + 1))
582 0 : return false;
583 184050 : BoolOption *bo = OffTheBooks::new_<BoolOption>(shortflag, longflag, help);
584 184050 : if (!bo)
585 0 : return false;
586 184050 : options.infallibleAppend(bo);
587 184050 : return true;
588 : }
589 :
590 : bool
591 18405 : OptionParser::addStringOption(char shortflag, const char *longflag, const char *metavar,
592 : const char *help)
593 : {
594 18405 : if (!options.reserve(options.length() + 1))
595 0 : return false;
596 18405 : StringOption *so = OffTheBooks::new_<StringOption>(shortflag, longflag, help, metavar);
597 18405 : if (!so)
598 0 : return false;
599 18405 : options.infallibleAppend(so);
600 18405 : return true;
601 : }
602 :
603 : bool
604 36810 : OptionParser::addMultiStringOption(char shortflag, const char *longflag, const char *metavar,
605 : const char *help)
606 : {
607 36810 : if (!options.reserve(options.length() + 1))
608 0 : return false;
609 : MultiStringOption *mso = OffTheBooks::new_<MultiStringOption>(shortflag, longflag, help,
610 36810 : metavar);
611 36810 : if (!mso)
612 0 : return false;
613 36810 : options.infallibleAppend(mso);
614 36810 : return true;
615 : }
616 :
617 : /* Argument builders */
618 :
619 : bool
620 18405 : OptionParser::addOptionalStringArg(const char *name, const char *help)
621 : {
622 18405 : if (!arguments.reserve(arguments.length() + 1))
623 0 : return false;
624 18405 : StringOption *so = OffTheBooks::new_<StringOption>(1, name, help, (const char *) NULL);
625 18405 : if (!so)
626 0 : return false;
627 18405 : arguments.infallibleAppend(so);
628 18405 : return true;
629 : }
630 :
631 : bool
632 18405 : OptionParser::addOptionalMultiStringArg(const char *name, const char *help)
633 : {
634 18405 : JS_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
635 18405 : if (!arguments.reserve(arguments.length() + 1))
636 0 : return false;
637 : MultiStringOption *mso = OffTheBooks::new_<MultiStringOption>(1, name, help,
638 18405 : (const char *) NULL);
639 18405 : if (!mso)
640 0 : return false;
641 18405 : arguments.infallibleAppend(mso);
642 18405 : return true;
643 : }
|