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"
11 #include <string_view>
13 #include "util/Unicode.h"
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); \
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
, ...) {
58 fprintf(stderr
, "Error: ");
59 vfprintf(stderr
, fmt
, args
);
61 fputs("\n\n", stderr
);
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
;
70 const char* it
= text
;
73 printf("%*s", int(startColno
), "");
76 /* Skip any leading spaces. */
77 while (*it
!= '\0' && unicode::IsSpace(*it
)) {
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') {
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
;
99 printf("%.*s", int(tokLen
), it
);
115 /* |text| wants to force a newline here. */
116 printf("\n%*s", int(startColno
), "");
119 /* Could also have line-leading spaces. */
129 MOZ_CRASH("unhandled token splitting character in text");
134 static const char* OptionFlagsToFormatInfo(char shortflag
, bool isValued
,
136 static const char* const fmt
[4] = {" -%c --%s ", " --%s ", " -%c --%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;
147 *length
= lengths
[index
];
151 OptionParser::Result
OptionParser::printHelp(const char* progname
) {
152 constexpr std::string_view prognameMeta
= "{progname}";
154 const char* prefixEnd
= strstr(usage
, prognameMeta
.data());
156 printf("%.*s%s%s\n", int(prefixEnd
- usage
), usage
, progname
,
157 prefixEnd
+ prognameMeta
.length());
164 PrintParagraph(descr
, 2, descrWidth
, true);
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;
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
) {
187 PrintParagraph(arg
->help
, lhsLen
, helpWidth
, false);
193 if (!options
.empty()) {
194 printf("Options:\n");
196 /* Calculate sizes for column alignment. */
198 for (Option
* opt
: options
) {
199 size_t longflagLen
= strlen(opt
->longflag
);
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
) {
215 OptionFlagsToFormatInfo(opt
->shortflag
, opt
->isValued(), &fmtLen
);
217 if (opt
->isValued()) {
218 if (opt
->shortflag
) {
219 chars
= printf(fmt
, opt
->shortflag
, opt
->longflag
,
220 opt
->asValued()->metavar
);
222 chars
= printf(fmt
, opt
->longflag
, opt
->asValued()->metavar
);
225 if (opt
->shortflag
) {
226 chars
= printf(fmt
, opt
->shortflag
, opt
->longflag
);
228 chars
= printf(fmt
, opt
->longflag
);
231 for (; chars
< lhsLen
; ++chars
) {
234 PrintParagraph(opt
->help
, lhsLen
, helpWidth
, false);
242 OptionParser::Result
OptionParser::printVersion() {
244 printf("%s\n", version
);
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
], '=');
254 if (*value
[0] == '\0') {
255 return error("A value is required for option %.*s", (int)(eq
- argv
[*i
]),
261 if (argc
== *i
+ 1) {
262 return error("Expected a value for option %s", argv
[*i
]);
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;
278 case OptionKindBool
: {
279 if (opt
== &helpOption
) {
280 return printHelp(argv
[0]);
282 if (opt
== &versionOption
) {
283 return printVersion();
285 opt
->asBoolOption()->value
= true;
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
)) {
297 opt
->asStringOption()->value
= value
;
300 case OptionKindInt
: {
301 char* value
= nullptr;
302 if (Result r
= extractValue(argc
, argv
, i
, &value
)) {
305 opt
->asIntOption()->value
= atoi(value
);
308 case OptionKindMultiString
: {
309 char* value
= nullptr;
310 if (Result r
= extractValue(argc
, argv
, i
, &value
)) {
313 StringArg
arg(value
, *i
);
314 return opt
->asMultiStringOption()->strings
.append(arg
) ? Okay
: Fail
;
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;
334 case OptionKindString
:
335 arg
->asStringOption()->value
= argv
[*i
];
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
;
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
354 bool optionsAllowed
= true;
356 for (size_t i
= 1; i
< argc
; ++i
) {
359 /* Note: solo dash option is actually a 'stdin' argument. */
360 if (arg
[0] == '-' && arg
[1] != '\0' && optionsAllowed
) {
364 if (arg
[2] == '\0') {
366 optionsAllowed
= false;
367 nextArgument
= restArgument
;
371 opt
= findOption(arg
+ 2);
373 return error("Invalid long option: %s", arg
);
378 if (arg
[2] != '\0') {
379 return error("Short option followed by junk: %s", arg
);
381 opt
= findOption(arg
[1]);
383 return error("Invalid short option: %s", arg
);
387 r
= handleOption(opt
, argc
, argv
, &i
, &optionsAllowed
);
390 r
= handleArg(argc
, argv
, &i
, &optionsAllowed
);
400 void OptionParser::setHelpOption(char shortflag
, const char* longflag
,
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
) {
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
);
473 fprintf(stderr
, "Failed to find short option %c\n", shortflag
);
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
]) {
490 if (longflag
[targetLen
] == '\0' || longflag
[targetLen
] == '=') {
494 if (strcmp(target
, longflag
) == 0) {
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
);
515 fprintf(stderr
, "Failed to find long option %s\n", longflag
);
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();
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
,
560 if (!options
.reserve(options
.length() + 1)) {
564 opt_new
<IntOption
>(shortflag
, longflag
, help
, metavar
, defaultValue
);
568 options
.infallibleAppend(io
);
572 bool OptionParser::addBoolOption(char shortflag
, const char* longflag
,
574 if (!options
.reserve(options
.length() + 1)) {
577 BoolOption
* bo
= opt_new
<BoolOption
>(shortflag
, longflag
, help
);
581 options
.infallibleAppend(bo
);
585 bool OptionParser::addStringOption(char shortflag
, const char* longflag
,
586 const char* metavar
, const char* help
) {
587 if (!options
.reserve(options
.length() + 1)) {
590 StringOption
* so
= opt_new
<StringOption
>(shortflag
, longflag
, help
, metavar
);
594 options
.infallibleAppend(so
);
598 bool OptionParser::addMultiStringOption(char shortflag
, const char* longflag
,
599 const char* metavar
, const char* help
) {
600 if (!options
.reserve(options
.length() + 1)) {
603 MultiStringOption
* mso
=
604 opt_new
<MultiStringOption
>(shortflag
, longflag
, help
, metavar
);
608 options
.infallibleAppend(mso
);
612 /* Argument builders */
614 bool OptionParser::addOptionalStringArg(const char* name
, const char* help
) {
615 if (!arguments
.reserve(arguments
.length() + 1)) {
618 StringOption
* so
= opt_new
<StringOption
>(1, name
, help
, (const char*)nullptr);
622 arguments
.infallibleAppend(so
);
626 bool OptionParser::addOptionalMultiStringArg(const char* name
,
628 MOZ_ASSERT_IF(!arguments
.empty(), !arguments
.back()->isVariadic());
629 if (!arguments
.reserve(arguments
.length() + 1)) {
632 MultiStringOption
* mso
=
633 opt_new
<MultiStringOption
>(1, name
, help
, (const char*)nullptr);
637 arguments
.infallibleAppend(mso
);