summaryrefslogtreecommitdiffstats
path: root/js/src/shell/jsoptparse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/shell/jsoptparse.cpp')
-rw-r--r--js/src/shell/jsoptparse.cpp616
1 files changed, 616 insertions, 0 deletions
diff --git a/js/src/shell/jsoptparse.cpp b/js/src/shell/jsoptparse.cpp
new file mode 100644
index 0000000000..14c45bfc6b
--- /dev/null
+++ b/js/src/shell/jsoptparse.cpp
@@ -0,0 +1,616 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "shell/jsoptparse.h"
+
+#include <algorithm>
+#include <stdarg.h>
+
+#include "util/Unicode.h"
+
+using namespace js;
+using namespace js::cli;
+using namespace js::cli::detail;
+
+const char OptionParser::prognameMeta[] = "{progname}";
+
+#define OPTION_CONVERT_IMPL(__cls) \
+ bool Option::is##__cls##Option() const { return kind == OptionKind##__cls; } \
+ __cls##Option* Option::as##__cls##Option() { \
+ MOZ_ASSERT(is##__cls##Option()); \
+ return static_cast<__cls##Option*>(this); \
+ } \
+ const __cls##Option* Option::as##__cls##Option() const { \
+ return const_cast<Option*>(this)->as##__cls##Option(); \
+ }
+
+ValuedOption* Option::asValued() {
+ MOZ_ASSERT(isValued());
+ return static_cast<ValuedOption*>(this);
+}
+
+const ValuedOption* Option::asValued() const {
+ return const_cast<Option*>(this)->asValued();
+}
+
+OPTION_CONVERT_IMPL(Bool)
+OPTION_CONVERT_IMPL(String)
+OPTION_CONVERT_IMPL(Int)
+OPTION_CONVERT_IMPL(MultiString)
+
+void OptionParser::setArgTerminatesOptions(const char* name, bool enabled) {
+ findArgument(name)->setTerminatesOptions(enabled);
+}
+
+void OptionParser::setArgCapturesRest(const char* name) {
+ MOZ_ASSERT(restArgument == -1,
+ "only one argument may be set to capture the rest");
+ restArgument = findArgumentIndex(name);
+ MOZ_ASSERT(restArgument != -1,
+ "unknown argument name passed to setArgCapturesRest");
+}
+
+OptionParser::Result OptionParser::error(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "Error: ");
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fputs("\n\n", stderr);
+ return ParseError;
+}
+
+/* Quick and dirty paragraph printer. */
+static void PrintParagraph(const char* text, unsigned startColno,
+ const unsigned limitColno, bool padFirstLine) {
+ unsigned colno = startColno;
+ unsigned indent = 0;
+ const char* it = text;
+
+ if (padFirstLine) {
+ printf("%*s", int(startColno), "");
+ }
+
+ /* Skip any leading spaces. */
+ while (*it != '\0' && unicode::IsSpace(*it)) {
+ ++it;
+ }
+
+ while (*it != '\0') {
+ MOZ_ASSERT(!unicode::IsSpace(*it) || *it == '\n');
+
+ /* Delimit the current token. */
+ const char* limit = it;
+ while (!unicode::IsSpace(*limit) && *limit != '\0') {
+ ++limit;
+ }
+
+ /*
+ * If the current token is longer than the available number of columns,
+ * then make a line break before printing the token.
+ */
+ size_t tokLen = limit - it;
+ if (tokLen + colno >= limitColno) {
+ printf("\n%*s%.*s", int(startColno + indent), "", int(tokLen), it);
+ colno = startColno + tokLen;
+ } else {
+ printf("%.*s", int(tokLen), it);
+ colno += tokLen;
+ }
+
+ switch (*limit) {
+ case '\0':
+ return;
+ case ' ':
+ putchar(' ');
+ colno += 1;
+ it = limit;
+ while (*it == ' ') {
+ ++it;
+ }
+ break;
+ case '\n':
+ /* |text| wants to force a newline here. */
+ printf("\n%*s", int(startColno), "");
+ colno = startColno;
+ it = limit + 1;
+ /* Could also have line-leading spaces. */
+ indent = 0;
+ while (*it == ' ') {
+ putchar(' ');
+ ++colno;
+ ++indent;
+ ++it;
+ }
+ break;
+ default:
+ MOZ_CRASH("unhandled token splitting character in text");
+ }
+ }
+}
+
+static const char* OptionFlagsToFormatInfo(char shortflag, bool isValued,
+ size_t* length) {
+ static const char* const fmt[4] = {" -%c --%s ", " --%s ", " -%c --%s=%s ",
+ " --%s=%s "};
+
+ /* How mny chars w/o longflag? */
+ size_t lengths[4] = {strlen(fmt[0]) - 3, strlen(fmt[1]) - 3,
+ strlen(fmt[2]) - 5, strlen(fmt[3]) - 5};
+ int index = isValued ? 2 : 0;
+ if (!shortflag) {
+ index++;
+ }
+
+ *length = lengths[index];
+ return fmt[index];
+}
+
+OptionParser::Result OptionParser::printHelp(const char* progname) {
+ const char* prefixEnd = strstr(usage, prognameMeta);
+ if (prefixEnd) {
+ printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
+ prefixEnd + sizeof(prognameMeta) - 1);
+ } else {
+ puts(usage);
+ }
+
+ if (descr) {
+ putchar('\n');
+ PrintParagraph(descr, 2, descrWidth, true);
+ putchar('\n');
+ }
+
+ if (version) {
+ printf("\nVersion: %s\n\n", version);
+ }
+
+ if (!arguments.empty()) {
+ printf("Arguments:\n");
+
+ static const char fmt[] = " %s ";
+ size_t fmtChars = sizeof(fmt) - 2;
+ size_t lhsLen = 0;
+ for (Option* arg : arguments) {
+ lhsLen = std::max(lhsLen, strlen(arg->longflag) + fmtChars);
+ }
+
+ for (Option* arg : arguments) {
+ size_t chars = printf(fmt, arg->longflag);
+ for (; chars < lhsLen; ++chars) {
+ putchar(' ');
+ }
+ PrintParagraph(arg->help, lhsLen, helpWidth, false);
+ putchar('\n');
+ }
+ putchar('\n');
+ }
+
+ if (!options.empty()) {
+ printf("Options:\n");
+
+ /* Calculate sizes for column alignment. */
+ size_t lhsLen = 0;
+ for (Option* opt : options) {
+ size_t longflagLen = strlen(opt->longflag);
+
+ size_t fmtLen;
+ OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
+
+ size_t len = fmtLen + longflagLen;
+ if (opt->isValued()) {
+ len += strlen(opt->asValued()->metavar);
+ }
+ lhsLen = std::max(lhsLen, len);
+ }
+
+ /* Print option help text. */
+ for (Option* opt : options) {
+ size_t fmtLen;
+ const char* fmt =
+ OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
+ size_t chars;
+ if (opt->isValued()) {
+ if (opt->shortflag) {
+ chars = printf(fmt, opt->shortflag, opt->longflag,
+ opt->asValued()->metavar);
+ } else {
+ chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
+ }
+ } else {
+ if (opt->shortflag) {
+ chars = printf(fmt, opt->shortflag, opt->longflag);
+ } else {
+ chars = printf(fmt, opt->longflag);
+ }
+ }
+ for (; chars < lhsLen; ++chars) {
+ putchar(' ');
+ }
+ PrintParagraph(opt->help, lhsLen, helpWidth, false);
+ putchar('\n');
+ }
+ }
+
+ return EarlyExit;
+}
+
+OptionParser::Result OptionParser::printVersion() {
+ MOZ_ASSERT(version);
+ printf("%s\n", version);
+ return EarlyExit;
+}
+
+OptionParser::Result OptionParser::extractValue(size_t argc, char** argv,
+ size_t* i, char** value) {
+ MOZ_ASSERT(*i < argc);
+ char* eq = strchr(argv[*i], '=');
+ if (eq) {
+ *value = eq + 1;
+ if (*value[0] == '\0') {
+ return error("A value is required for option %.*s", (int)(eq - argv[*i]),
+ argv[*i]);
+ }
+ return Okay;
+ }
+
+ if (argc == *i + 1) {
+ return error("Expected a value for option %s", argv[*i]);
+ }
+
+ *i += 1;
+ *value = argv[*i];
+ return Okay;
+}
+
+OptionParser::Result OptionParser::handleOption(Option* opt, size_t argc,
+ char** argv, size_t* i,
+ bool* optionsAllowed) {
+ if (opt->getTerminatesOptions()) {
+ *optionsAllowed = false;
+ }
+
+ switch (opt->kind) {
+ case OptionKindBool: {
+ if (opt == &helpOption) {
+ return printHelp(argv[0]);
+ }
+ if (opt == &versionOption) {
+ return printVersion();
+ }
+ opt->asBoolOption()->value = true;
+ return Okay;
+ }
+ /*
+ * Valued options are allowed to specify their values either via
+ * successive arguments or a single --longflag=value argument.
+ */
+ case OptionKindString: {
+ char* value = nullptr;
+ if (Result r = extractValue(argc, argv, i, &value)) {
+ return r;
+ }
+ opt->asStringOption()->value = value;
+ return Okay;
+ }
+ case OptionKindInt: {
+ char* value = nullptr;
+ if (Result r = extractValue(argc, argv, i, &value)) {
+ return r;
+ }
+ opt->asIntOption()->value = atoi(value);
+ return Okay;
+ }
+ case OptionKindMultiString: {
+ char* value = nullptr;
+ if (Result r = extractValue(argc, argv, i, &value)) {
+ return r;
+ }
+ StringArg arg(value, *i);
+ return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
+ }
+ default:
+ MOZ_CRASH("unhandled option kind");
+ }
+}
+
+OptionParser::Result OptionParser::handleArg(size_t argc, char** argv,
+ size_t* i, bool* optionsAllowed) {
+ if (nextArgument >= arguments.length()) {
+ return error("Too many arguments provided");
+ }
+
+ Option* arg = arguments[nextArgument];
+
+ if (arg->getTerminatesOptions()) {
+ *optionsAllowed = false;
+ }
+
+ switch (arg->kind) {
+ case OptionKindString:
+ arg->asStringOption()->value = argv[*i];
+ nextArgument += 1;
+ return Okay;
+ case OptionKindMultiString: {
+ // Don't advance the next argument -- there can only be one (final)
+ // variadic argument.
+ StringArg value(argv[*i], *i);
+ return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
+ }
+ default:
+ MOZ_CRASH("unhandled argument kind");
+ }
+}
+
+OptionParser::Result OptionParser::parseArgs(int inputArgc, char** argv) {
+ MOZ_ASSERT(inputArgc >= 0);
+ size_t argc = inputArgc;
+ // Permit a "no more options" capability, like |--| offers in many shell
+ // interfaces.
+ bool optionsAllowed = true;
+
+ for (size_t i = 1; i < argc; ++i) {
+ char* arg = argv[i];
+ Result r;
+ /* Note: solo dash option is actually a 'stdin' argument. */
+ if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
+ /* Option. */
+ Option* opt;
+ if (arg[1] == '-') {
+ if (arg[2] == '\0') {
+ /* End of options */
+ optionsAllowed = false;
+ nextArgument = restArgument;
+ continue;
+ } else {
+ /* Long option. */
+ opt = findOption(arg + 2);
+ if (!opt) {
+ return error("Invalid long option: %s", arg);
+ }
+ }
+ } else {
+ /* Short option */
+ if (arg[2] != '\0') {
+ return error("Short option followed by junk: %s", arg);
+ }
+ opt = findOption(arg[1]);
+ if (!opt) {
+ return error("Invalid short option: %s", arg);
+ }
+ }
+
+ r = handleOption(opt, argc, argv, &i, &optionsAllowed);
+ } else {
+ /* Argument. */
+ r = handleArg(argc, argv, &i, &optionsAllowed);
+ }
+
+ if (r != Okay) {
+ return r;
+ }
+ }
+ return Okay;
+}
+
+void OptionParser::setHelpOption(char shortflag, const char* longflag,
+ const char* help) {
+ helpOption.setFlagInfo(shortflag, longflag, help);
+}
+
+bool OptionParser::getHelpOption() const { return helpOption.value; }
+
+bool OptionParser::getBoolOption(char shortflag) const {
+ return findOption(shortflag)->asBoolOption()->value;
+}
+
+int OptionParser::getIntOption(char shortflag) const {
+ return findOption(shortflag)->asIntOption()->value;
+}
+
+const char* OptionParser::getStringOption(char shortflag) const {
+ return findOption(shortflag)->asStringOption()->value;
+}
+
+MultiStringRange OptionParser::getMultiStringOption(char shortflag) const {
+ const MultiStringOption* mso = findOption(shortflag)->asMultiStringOption();
+ return MultiStringRange(mso->strings.begin(), mso->strings.end());
+}
+
+bool OptionParser::getBoolOption(const char* longflag) const {
+ return findOption(longflag)->asBoolOption()->value;
+}
+
+int OptionParser::getIntOption(const char* longflag) const {
+ return findOption(longflag)->asIntOption()->value;
+}
+
+const char* OptionParser::getStringOption(const char* longflag) const {
+ return findOption(longflag)->asStringOption()->value;
+}
+
+MultiStringRange OptionParser::getMultiStringOption(
+ const char* longflag) const {
+ const MultiStringOption* mso = findOption(longflag)->asMultiStringOption();
+ return MultiStringRange(mso->strings.begin(), mso->strings.end());
+}
+
+OptionParser::~OptionParser() {
+ for (Option* opt : options) {
+ js_delete<Option>(opt);
+ }
+ for (Option* arg : arguments) {
+ js_delete<Option>(arg);
+ }
+}
+
+Option* OptionParser::findOption(char shortflag) {
+ for (Option* opt : options) {
+ if (opt->shortflag == shortflag) {
+ return opt;
+ }
+ }
+
+ if (versionOption.shortflag == shortflag) {
+ return &versionOption;
+ }
+
+ return helpOption.shortflag == shortflag ? &helpOption : nullptr;
+}
+
+const Option* OptionParser::findOption(char shortflag) const {
+ return const_cast<OptionParser*>(this)->findOption(shortflag);
+}
+
+Option* OptionParser::findOption(const char* longflag) {
+ for (Option* opt : options) {
+ const char* target = opt->longflag;
+ if (opt->isValued()) {
+ size_t targetLen = strlen(target);
+ /* Permit a trailing equals sign on the longflag argument. */
+ for (size_t i = 0; i < targetLen; ++i) {
+ if (longflag[i] == '\0' || longflag[i] != target[i]) {
+ goto no_match;
+ }
+ }
+ if (longflag[targetLen] == '\0' || longflag[targetLen] == '=') {
+ return opt;
+ }
+ } else {
+ if (strcmp(target, longflag) == 0) {
+ return opt;
+ }
+ }
+ no_match:;
+ }
+
+ if (strcmp(versionOption.longflag, longflag) == 0) {
+ return &versionOption;
+ }
+
+ return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
+}
+
+const Option* OptionParser::findOption(const char* longflag) const {
+ return const_cast<OptionParser*>(this)->findOption(longflag);
+}
+
+/* Argument accessors */
+
+int OptionParser::findArgumentIndex(const char* name) const {
+ for (Option* const* it = arguments.begin(); it != arguments.end(); ++it) {
+ const char* target = (*it)->longflag;
+ if (strcmp(target, name) == 0) {
+ return it - arguments.begin();
+ }
+ }
+ return -1;
+}
+
+Option* OptionParser::findArgument(const char* name) {
+ int index = findArgumentIndex(name);
+ return (index == -1) ? nullptr : arguments[index];
+}
+
+const Option* OptionParser::findArgument(const char* name) const {
+ int index = findArgumentIndex(name);
+ return (index == -1) ? nullptr : arguments[index];
+}
+
+const char* OptionParser::getStringArg(const char* name) const {
+ return findArgument(name)->asStringOption()->value;
+}
+
+MultiStringRange OptionParser::getMultiStringArg(const char* name) const {
+ const MultiStringOption* mso = findArgument(name)->asMultiStringOption();
+ return MultiStringRange(mso->strings.begin(), mso->strings.end());
+}
+
+/* Option builders */
+
+bool OptionParser::addIntOption(char shortflag, const char* longflag,
+ const char* metavar, const char* help,
+ int defaultValue) {
+ if (!options.reserve(options.length() + 1)) {
+ return false;
+ }
+ IntOption* io =
+ js_new<IntOption>(shortflag, longflag, help, metavar, defaultValue);
+ if (!io) {
+ return false;
+ }
+ options.infallibleAppend(io);
+ return true;
+}
+
+bool OptionParser::addBoolOption(char shortflag, const char* longflag,
+ const char* help) {
+ if (!options.reserve(options.length() + 1)) {
+ return false;
+ }
+ BoolOption* bo = js_new<BoolOption>(shortflag, longflag, help);
+ if (!bo) {
+ return false;
+ }
+ options.infallibleAppend(bo);
+ return true;
+}
+
+bool OptionParser::addStringOption(char shortflag, const char* longflag,
+ const char* metavar, const char* help) {
+ if (!options.reserve(options.length() + 1)) {
+ return false;
+ }
+ StringOption* so = js_new<StringOption>(shortflag, longflag, help, metavar);
+ if (!so) {
+ return false;
+ }
+ options.infallibleAppend(so);
+ return true;
+}
+
+bool OptionParser::addMultiStringOption(char shortflag, const char* longflag,
+ const char* metavar, const char* help) {
+ if (!options.reserve(options.length() + 1)) {
+ return false;
+ }
+ MultiStringOption* mso =
+ js_new<MultiStringOption>(shortflag, longflag, help, metavar);
+ if (!mso) {
+ return false;
+ }
+ options.infallibleAppend(mso);
+ return true;
+}
+
+/* Argument builders */
+
+bool OptionParser::addOptionalStringArg(const char* name, const char* help) {
+ if (!arguments.reserve(arguments.length() + 1)) {
+ return false;
+ }
+ StringOption* so = js_new<StringOption>(1, name, help, (const char*)nullptr);
+ if (!so) {
+ return false;
+ }
+ arguments.infallibleAppend(so);
+ return true;
+}
+
+bool OptionParser::addOptionalMultiStringArg(const char* name,
+ const char* help) {
+ MOZ_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
+ if (!arguments.reserve(arguments.length() + 1)) {
+ return false;
+ }
+ MultiStringOption* mso =
+ js_new<MultiStringOption>(1, name, help, (const char*)nullptr);
+ if (!mso) {
+ return false;
+ }
+ arguments.infallibleAppend(mso);
+ return true;
+}