no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / js / src / shell / jsoptparse.cpp
blob5632598d7d373904652e67a2682d4c85b3abeb89
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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 <algorithm>
10 #include <stdarg.h>
11 #include <string_view>
13 #include "util/Unicode.h"
15 using namespace js;
16 using namespace js::cli;
17 using namespace js::cli::detail;
19 #define OPTION_CONVERT_IMPL(__cls) \
20 bool Option::is##__cls##Option() const { return kind == OptionKind##__cls; } \
21 __cls##Option* Option::as##__cls##Option() { \
22 MOZ_ASSERT(is##__cls##Option()); \
23 return static_cast<__cls##Option*>(this); \
24 } \
25 const __cls##Option* Option::as##__cls##Option() const { \
26 return const_cast<Option*>(this)->as##__cls##Option(); \
29 ValuedOption* Option::asValued() {
30 MOZ_ASSERT(isValued());
31 return static_cast<ValuedOption*>(this);
34 const ValuedOption* Option::asValued() const {
35 return const_cast<Option*>(this)->asValued();
38 OPTION_CONVERT_IMPL(Bool)
39 OPTION_CONVERT_IMPL(String)
40 OPTION_CONVERT_IMPL(Int)
41 OPTION_CONVERT_IMPL(MultiString)
43 void OptionParser::setArgTerminatesOptions(const char* name, bool enabled) {
44 findArgument(name)->setTerminatesOptions(enabled);
47 void OptionParser::setArgCapturesRest(const char* name) {
48 MOZ_ASSERT(restArgument == -1,
49 "only one argument may be set to capture the rest");
50 restArgument = findArgumentIndex(name);
51 MOZ_ASSERT(restArgument != -1,
52 "unknown argument name passed to setArgCapturesRest");
55 OptionParser::Result OptionParser::error(const char* fmt, ...) {
56 va_list args;
57 va_start(args, fmt);
58 fprintf(stderr, "Error: ");
59 vfprintf(stderr, fmt, args);
60 va_end(args);
61 fputs("\n\n", stderr);
62 return ParseError;
65 /* Quick and dirty paragraph printer. */
66 static void PrintParagraph(const char* text, unsigned startColno,
67 const unsigned limitColno, bool padFirstLine) {
68 unsigned colno = startColno;
69 unsigned indent = 0;
70 const char* it = text;
72 if (padFirstLine) {
73 printf("%*s", int(startColno), "");
76 /* Skip any leading spaces. */
77 while (*it != '\0' && unicode::IsSpace(*it)) {
78 ++it;
81 while (*it != '\0') {
82 MOZ_ASSERT(!unicode::IsSpace(*it) || *it == '\n');
84 /* Delimit the current token. */
85 const char* limit = it;
86 while (!unicode::IsSpace(*limit) && *limit != '\0') {
87 ++limit;
91 * If the current token is longer than the available number of columns,
92 * then make a line break before printing the token.
94 size_t tokLen = limit - it;
95 if (tokLen + colno >= limitColno) {
96 printf("\n%*s%.*s", int(startColno + indent), "", int(tokLen), it);
97 colno = startColno + tokLen;
98 } else {
99 printf("%.*s", int(tokLen), it);
100 colno += tokLen;
103 switch (*limit) {
104 case '\0':
105 return;
106 case ' ':
107 putchar(' ');
108 colno += 1;
109 it = limit;
110 while (*it == ' ') {
111 ++it;
113 break;
114 case '\n':
115 /* |text| wants to force a newline here. */
116 printf("\n%*s", int(startColno), "");
117 colno = startColno;
118 it = limit + 1;
119 /* Could also have line-leading spaces. */
120 indent = 0;
121 while (*it == ' ') {
122 putchar(' ');
123 ++colno;
124 ++indent;
125 ++it;
127 break;
128 default:
129 MOZ_CRASH("unhandled token splitting character in text");
134 static const char* OptionFlagsToFormatInfo(char shortflag, bool isValued,
135 size_t* length) {
136 static const char* const fmt[4] = {" -%c --%s ", " --%s ", " -%c --%s=%s ",
137 " --%s=%s "};
139 /* How mny chars w/o longflag? */
140 size_t lengths[4] = {strlen(fmt[0]) - 3, strlen(fmt[1]) - 3,
141 strlen(fmt[2]) - 5, strlen(fmt[3]) - 5};
142 int index = isValued ? 2 : 0;
143 if (!shortflag) {
144 index++;
147 *length = lengths[index];
148 return fmt[index];
151 OptionParser::Result OptionParser::printHelp(const char* progname) {
152 constexpr std::string_view prognameMeta = "{progname}";
154 const char* prefixEnd = strstr(usage, prognameMeta.data());
155 if (prefixEnd) {
156 printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
157 prefixEnd + prognameMeta.length());
158 } else {
159 puts(usage);
162 if (descr) {
163 putchar('\n');
164 PrintParagraph(descr, 2, descrWidth, true);
165 putchar('\n');
168 if (version) {
169 printf("\nVersion: %s\n\n", version);
172 if (!arguments.empty()) {
173 printf("Arguments:\n");
175 static const char fmt[] = " %s ";
176 size_t fmtChars = sizeof(fmt) - 2;
177 size_t lhsLen = 0;
178 for (Option* arg : arguments) {
179 lhsLen = std::max(lhsLen, strlen(arg->longflag) + fmtChars);
182 for (Option* arg : arguments) {
183 size_t chars = printf(fmt, arg->longflag);
184 for (; chars < lhsLen; ++chars) {
185 putchar(' ');
187 PrintParagraph(arg->help, lhsLen, helpWidth, false);
188 putchar('\n');
190 putchar('\n');
193 if (!options.empty()) {
194 printf("Options:\n");
196 /* Calculate sizes for column alignment. */
197 size_t lhsLen = 0;
198 for (Option* opt : options) {
199 size_t longflagLen = strlen(opt->longflag);
201 size_t fmtLen;
202 OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
204 size_t len = fmtLen + longflagLen;
205 if (opt->isValued()) {
206 len += strlen(opt->asValued()->metavar);
208 lhsLen = std::max(lhsLen, len);
211 /* Print option help text. */
212 for (Option* opt : options) {
213 size_t fmtLen;
214 const char* fmt =
215 OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
216 size_t chars;
217 if (opt->isValued()) {
218 if (opt->shortflag) {
219 chars = printf(fmt, opt->shortflag, opt->longflag,
220 opt->asValued()->metavar);
221 } else {
222 chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
224 } else {
225 if (opt->shortflag) {
226 chars = printf(fmt, opt->shortflag, opt->longflag);
227 } else {
228 chars = printf(fmt, opt->longflag);
231 for (; chars < lhsLen; ++chars) {
232 putchar(' ');
234 PrintParagraph(opt->help, lhsLen, helpWidth, false);
235 putchar('\n');
239 return EarlyExit;
242 OptionParser::Result OptionParser::printVersion() {
243 MOZ_ASSERT(version);
244 printf("%s\n", version);
245 return EarlyExit;
248 OptionParser::Result OptionParser::extractValue(size_t argc, char** argv,
249 size_t* i, char** value) {
250 MOZ_ASSERT(*i < argc);
251 char* eq = strchr(argv[*i], '=');
252 if (eq) {
253 *value = eq + 1;
254 if (*value[0] == '\0') {
255 return error("A value is required for option %.*s", (int)(eq - argv[*i]),
256 argv[*i]);
258 return Okay;
261 if (argc == *i + 1) {
262 return error("Expected a value for option %s", argv[*i]);
265 *i += 1;
266 *value = argv[*i];
267 return Okay;
270 OptionParser::Result OptionParser::handleOption(Option* opt, size_t argc,
271 char** argv, size_t* i,
272 bool* optionsAllowed) {
273 if (opt->getTerminatesOptions()) {
274 *optionsAllowed = false;
277 switch (opt->kind) {
278 case OptionKindBool: {
279 if (opt == &helpOption) {
280 return printHelp(argv[0]);
282 if (opt == &versionOption) {
283 return printVersion();
285 opt->asBoolOption()->value = true;
286 return Okay;
289 * Valued options are allowed to specify their values either via
290 * successive arguments or a single --longflag=value argument.
292 case OptionKindString: {
293 char* value = nullptr;
294 if (Result r = extractValue(argc, argv, i, &value)) {
295 return r;
297 opt->asStringOption()->value = value;
298 return Okay;
300 case OptionKindInt: {
301 char* value = nullptr;
302 if (Result r = extractValue(argc, argv, i, &value)) {
303 return r;
305 opt->asIntOption()->value = atoi(value);
306 return Okay;
308 case OptionKindMultiString: {
309 char* value = nullptr;
310 if (Result r = extractValue(argc, argv, i, &value)) {
311 return r;
313 StringArg arg(value, *i);
314 return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
316 default:
317 MOZ_CRASH("unhandled option kind");
321 OptionParser::Result OptionParser::handleArg(size_t argc, char** argv,
322 size_t* i, bool* optionsAllowed) {
323 if (nextArgument >= arguments.length()) {
324 return error("Too many arguments provided");
327 Option* arg = arguments[nextArgument];
329 if (arg->getTerminatesOptions()) {
330 *optionsAllowed = false;
333 switch (arg->kind) {
334 case OptionKindString:
335 arg->asStringOption()->value = argv[*i];
336 nextArgument += 1;
337 return Okay;
338 case OptionKindMultiString: {
339 // Don't advance the next argument -- there can only be one (final)
340 // variadic argument.
341 StringArg value(argv[*i], *i);
342 return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
344 default:
345 MOZ_CRASH("unhandled argument kind");
349 OptionParser::Result OptionParser::parseArgs(int inputArgc, char** argv) {
350 MOZ_ASSERT(inputArgc >= 0);
351 size_t argc = inputArgc;
352 // Permit a "no more options" capability, like |--| offers in many shell
353 // interfaces.
354 bool optionsAllowed = true;
356 for (size_t i = 1; i < argc; ++i) {
357 char* arg = argv[i];
358 Result r;
359 /* Note: solo dash option is actually a 'stdin' argument. */
360 if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
361 /* Option. */
362 Option* opt;
363 if (arg[1] == '-') {
364 if (arg[2] == '\0') {
365 /* End of options */
366 optionsAllowed = false;
367 nextArgument = restArgument;
368 continue;
369 } else {
370 /* Long option. */
371 opt = findOption(arg + 2);
372 if (!opt) {
373 return error("Invalid long option: %s", arg);
376 } else {
377 /* Short option */
378 if (arg[2] != '\0') {
379 return error("Short option followed by junk: %s", arg);
381 opt = findOption(arg[1]);
382 if (!opt) {
383 return error("Invalid short option: %s", arg);
387 r = handleOption(opt, argc, argv, &i, &optionsAllowed);
388 } else {
389 /* Argument. */
390 r = handleArg(argc, argv, &i, &optionsAllowed);
393 if (r != Okay) {
394 return r;
397 return Okay;
400 void OptionParser::setHelpOption(char shortflag, const char* longflag,
401 const char* help) {
402 helpOption.setFlagInfo(shortflag, longflag, help);
405 bool OptionParser::getHelpOption() const { return helpOption.value; }
407 bool OptionParser::getBoolOption(char shortflag) const {
408 return tryFindOption(shortflag)->asBoolOption()->value;
411 int OptionParser::getIntOption(char shortflag) const {
412 return tryFindOption(shortflag)->asIntOption()->value;
415 const char* OptionParser::getStringOption(char shortflag) const {
416 return tryFindOption(shortflag)->asStringOption()->value;
419 MultiStringRange OptionParser::getMultiStringOption(char shortflag) const {
420 const MultiStringOption* mso =
421 tryFindOption(shortflag)->asMultiStringOption();
422 return MultiStringRange(mso->strings.begin(), mso->strings.end());
425 bool OptionParser::getBoolOption(const char* longflag) const {
426 return tryFindOption(longflag)->asBoolOption()->value;
429 int OptionParser::getIntOption(const char* longflag) const {
430 return tryFindOption(longflag)->asIntOption()->value;
433 const char* OptionParser::getStringOption(const char* longflag) const {
434 return tryFindOption(longflag)->asStringOption()->value;
437 MultiStringRange OptionParser::getMultiStringOption(
438 const char* longflag) const {
439 const MultiStringOption* mso = tryFindOption(longflag)->asMultiStringOption();
440 return MultiStringRange(mso->strings.begin(), mso->strings.end());
443 OptionParser::~OptionParser() {
444 for (Option* opt : options) {
445 js_delete<Option>(opt);
447 for (Option* arg : arguments) {
448 js_delete<Option>(arg);
452 Option* OptionParser::findOption(char shortflag) {
453 for (Option* opt : options) {
454 if (opt->shortflag == shortflag) {
455 return opt;
459 if (versionOption.shortflag == shortflag) {
460 return &versionOption;
463 return helpOption.shortflag == shortflag ? &helpOption : nullptr;
466 const Option* OptionParser::findOption(char shortflag) const {
467 return const_cast<OptionParser*>(this)->findOption(shortflag);
470 const Option* OptionParser::tryFindOption(char shortflag) const {
471 const Option* maybeOption = findOption(shortflag);
472 if (!maybeOption) {
473 fprintf(stderr, "Failed to find short option %c\n", shortflag);
474 MOZ_CRASH();
476 return maybeOption;
479 Option* OptionParser::findOption(const char* longflag) {
480 for (Option* opt : options) {
481 const char* target = opt->longflag;
482 if (opt->isValued()) {
483 size_t targetLen = strlen(target);
484 /* Permit a trailing equals sign on the longflag argument. */
485 for (size_t i = 0; i < targetLen; ++i) {
486 if (longflag[i] == '\0' || longflag[i] != target[i]) {
487 goto no_match;
490 if (longflag[targetLen] == '\0' || longflag[targetLen] == '=') {
491 return opt;
493 } else {
494 if (strcmp(target, longflag) == 0) {
495 return opt;
498 no_match:;
501 if (strcmp(versionOption.longflag, longflag) == 0) {
502 return &versionOption;
505 return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
508 const Option* OptionParser::findOption(const char* longflag) const {
509 return const_cast<OptionParser*>(this)->findOption(longflag);
512 const Option* OptionParser::tryFindOption(const char* longflag) const {
513 const Option* maybeOption = findOption(longflag);
514 if (!maybeOption) {
515 fprintf(stderr, "Failed to find long option %s\n", longflag);
516 MOZ_CRASH();
518 return maybeOption;
521 /* Argument accessors */
523 int OptionParser::findArgumentIndex(const char* name) const {
524 for (Option* const* it = arguments.begin(); it != arguments.end(); ++it) {
525 const char* target = (*it)->longflag;
526 if (strcmp(target, name) == 0) {
527 return it - arguments.begin();
530 return -1;
533 Option* OptionParser::findArgument(const char* name) {
534 int index = findArgumentIndex(name);
535 return (index == -1) ? nullptr : arguments[index];
538 const Option* OptionParser::findArgument(const char* name) const {
539 int index = findArgumentIndex(name);
540 return (index == -1) ? nullptr : arguments[index];
543 const char* OptionParser::getStringArg(const char* name) const {
544 return findArgument(name)->asStringOption()->value;
547 MultiStringRange OptionParser::getMultiStringArg(const char* name) const {
548 const MultiStringOption* mso = findArgument(name)->asMultiStringOption();
549 return MultiStringRange(mso->strings.begin(), mso->strings.end());
552 /* Option builders */
554 // Use vanilla malloc for allocations. See OptionAllocPolicy.
555 JS_DECLARE_NEW_METHODS(opt_new, malloc, static MOZ_ALWAYS_INLINE)
557 bool OptionParser::addIntOption(char shortflag, const char* longflag,
558 const char* metavar, const char* help,
559 int defaultValue) {
560 if (!options.reserve(options.length() + 1)) {
561 return false;
563 IntOption* io =
564 opt_new<IntOption>(shortflag, longflag, help, metavar, defaultValue);
565 if (!io) {
566 return false;
568 options.infallibleAppend(io);
569 return true;
572 bool OptionParser::addBoolOption(char shortflag, const char* longflag,
573 const char* help) {
574 if (!options.reserve(options.length() + 1)) {
575 return false;
577 BoolOption* bo = opt_new<BoolOption>(shortflag, longflag, help);
578 if (!bo) {
579 return false;
581 options.infallibleAppend(bo);
582 return true;
585 bool OptionParser::addStringOption(char shortflag, const char* longflag,
586 const char* metavar, const char* help) {
587 if (!options.reserve(options.length() + 1)) {
588 return false;
590 StringOption* so = opt_new<StringOption>(shortflag, longflag, help, metavar);
591 if (!so) {
592 return false;
594 options.infallibleAppend(so);
595 return true;
598 bool OptionParser::addMultiStringOption(char shortflag, const char* longflag,
599 const char* metavar, const char* help) {
600 if (!options.reserve(options.length() + 1)) {
601 return false;
603 MultiStringOption* mso =
604 opt_new<MultiStringOption>(shortflag, longflag, help, metavar);
605 if (!mso) {
606 return false;
608 options.infallibleAppend(mso);
609 return true;
612 /* Argument builders */
614 bool OptionParser::addOptionalStringArg(const char* name, const char* help) {
615 if (!arguments.reserve(arguments.length() + 1)) {
616 return false;
618 StringOption* so = opt_new<StringOption>(1, name, help, (const char*)nullptr);
619 if (!so) {
620 return false;
622 arguments.infallibleAppend(so);
623 return true;
626 bool OptionParser::addOptionalMultiStringArg(const char* name,
627 const char* help) {
628 MOZ_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
629 if (!arguments.reserve(arguments.length() + 1)) {
630 return false;
632 MultiStringOption* mso =
633 opt_new<MultiStringOption>(1, name, help, (const char*)nullptr);
634 if (!mso) {
635 return false;
637 arguments.infallibleAppend(mso);
638 return true;