js/src/shell/jsoptparse.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:a233f27d0f49
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/. */
6
7 #include "shell/jsoptparse.h"
8
9 #include <ctype.h>
10 #include <stdarg.h>
11
12 #include "jsutil.h"
13
14 using namespace js;
15 using namespace js::cli;
16 using namespace js::cli::detail;
17
18 const char OptionParser::prognameMeta[] = "{progname}";
19
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 }
37
38 ValuedOption *
39 Option::asValued()
40 {
41 JS_ASSERT(isValued());
42 return static_cast<ValuedOption *>(this);
43 }
44
45 const ValuedOption *
46 Option::asValued() const
47 {
48 return const_cast<Option *>(this)->asValued();
49 }
50
51 OPTION_CONVERT_IMPL(Bool)
52 OPTION_CONVERT_IMPL(String)
53 OPTION_CONVERT_IMPL(Int)
54 OPTION_CONVERT_IMPL(MultiString)
55
56 void
57 OptionParser::setArgTerminatesOptions(const char *name, bool enabled)
58 {
59 findArgument(name)->setTerminatesOptions(enabled);
60 }
61
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 }
69
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 }
81
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;
88
89 if (padFirstLine)
90 printf("%*s", startColno, "");
91
92 while (*it != '\0') {
93 JS_ASSERT(!isspace(*it));
94
95 /* Delimit the current token. */
96 const char *limit = it;
97 while (!isspace(*limit) && *limit != '\0')
98 ++limit;
99
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 }
114
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 }
142
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 " };
150
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++;
159
160 *length = lengths[index];
161 return fmt[index];
162 }
163
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 }
174
175 if (descr) {
176 putchar('\n');
177 PrintParagraph(descr, 2, descrWidth, true);
178 putchar('\n');
179 }
180
181 if (ver)
182 printf("\nVersion: %s\n\n", ver);
183
184 if (!arguments.empty()) {
185 printf("Arguments:\n");
186
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);
192
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 }
203
204 if (!options.empty()) {
205 printf("Options:\n");
206
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);
212
213 size_t fmtLen;
214 OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
215
216 size_t len = fmtLen + longflagLen;
217 if (opt->isValued())
218 len += strlen(opt->asValued()->metavar);
219 lhsLen = Max(lhsLen, len);
220 }
221
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 }
245
246 return ParseHelp;
247 }
248
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 }
260
261 if (argc == *i + 1)
262 return error("Expected a value for option %s", argv[*i]);
263
264 *i += 1;
265 *value = argv[*i];
266 return Okay;
267 }
268
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;
274
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 }
315
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");
321
322 Option *arg = arguments[nextArgument];
323
324 if (arg->getTerminatesOptions())
325 *optionsAllowed = false;
326
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 }
342
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;
350
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 }
378
379 r = handleOption(opt, argc, argv, &i, &optionsAllowed);
380 } else {
381 /* Argument. */
382 r = handleArg(argc, argv, &i, &optionsAllowed);
383 }
384
385 if (r != Okay)
386 return r;
387 }
388 return Okay;
389 }
390
391 void
392 OptionParser::setHelpOption(char shortflag, const char *longflag, const char *help)
393 {
394 helpOption.setFlagInfo(shortflag, longflag, help);
395 }
396
397 bool
398 OptionParser::getHelpOption() const
399 {
400 return helpOption.value;
401 }
402
403 bool
404 OptionParser::getBoolOption(char shortflag) const
405 {
406 return findOption(shortflag)->asBoolOption()->value;
407 }
408
409 int
410 OptionParser::getIntOption(char shortflag) const
411 {
412 return findOption(shortflag)->asIntOption()->value;
413 }
414
415 const char *
416 OptionParser::getStringOption(char shortflag) const
417 {
418 return findOption(shortflag)->asStringOption()->value;
419 }
420
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 }
427
428 bool
429 OptionParser::getBoolOption(const char *longflag) const
430 {
431 return findOption(longflag)->asBoolOption()->value;
432 }
433
434 int
435 OptionParser::getIntOption(const char *longflag) const
436 {
437 return findOption(longflag)->asIntOption()->value;
438 }
439
440 const char *
441 OptionParser::getStringOption(const char *longflag) const
442 {
443 return findOption(longflag)->asStringOption()->value;
444 }
445
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 }
452
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 }
460
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 }
468
469 return helpOption.shortflag == shortflag ? &helpOption : nullptr;
470 }
471
472 const Option *
473 OptionParser::findOption(char shortflag) const
474 {
475 return const_cast<OptionParser *>(this)->findOption(shortflag);
476 }
477
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 }
498
499 return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
500 }
501
502 const Option *
503 OptionParser::findOption(const char *longflag) const
504 {
505 return const_cast<OptionParser *>(this)->findOption(longflag);
506 }
507
508 /* Argument accessors */
509
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 }
520
521 Option *
522 OptionParser::findArgument(const char *name)
523 {
524 int index = findArgumentIndex(name);
525 return (index == -1) ? nullptr : arguments[index];
526 }
527
528 const Option *
529 OptionParser::findArgument(const char *name) const
530 {
531 int index = findArgumentIndex(name);
532 return (index == -1) ? nullptr : arguments[index];
533 }
534
535 const char *
536 OptionParser::getStringArg(const char *name) const
537 {
538 return findArgument(name)->asStringOption()->value;
539 }
540
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 }
547
548 /* Option builders */
549
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 }
562
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 }
574
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 }
587
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 }
600
601 /* Argument builders */
602
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 }
614
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