mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-09 11:29:39 +00:00
647 lines
17 KiB
C++
647 lines
17 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* 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 <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "jsutil.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", startColno, "");
|
|
|
|
/* Skip any leading spaces. */
|
|
while (*it != '\0' && isspace(*it))
|
|
++it;
|
|
|
|
while (*it != '\0') {
|
|
MOZ_ASSERT(!isspace(*it));
|
|
|
|
/* Delimit the current token. */
|
|
const char* limit = it;
|
|
while (!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.
|
|
*/
|
|
MOZ_ASSERT(limit - it > 0);
|
|
size_t tokLen = limit - it;
|
|
MOZ_ASSERT(tokLen);
|
|
if (tokLen + colno >= limitColno) {
|
|
printf("\n%*s%.*s", 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", 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 = 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 = 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", 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;
|
|
}
|