From ae24c9056d05b5f3ebe90cf4c345a986a6539bf4 Mon Sep 17 00:00:00 2001 From: JF Bastien Date: Sat, 2 Jan 2016 13:49:11 -0800 Subject: Generalize command-line parsing more. This should allow other programs to use the same command-line support. --- src/support/command-line.cpp | 146 ++++++++++++++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 38 deletions(-) (limited to 'src/support/command-line.cpp') diff --git a/src/support/command-line.cpp b/src/support/command-line.cpp index 3c03d48f4..9b844ff1e 100644 --- a/src/support/command-line.cpp +++ b/src/support/command-line.cpp @@ -16,50 +16,120 @@ #include "support/command-line.h" -namespace { -bool optionIs(const char *arg, const char *LongOpt, const char *ShortOpt) { - return strcmp(arg, LongOpt) == 0 || strcmp(arg, ShortOpt) == 0; +using namespace wasm; + +Options::Options(const std::string &command, const std::string &description) + : debug(false), positional(Arguments::Zero) { + add("--help", "-h", "Show this help message and exit", Arguments::Zero, + [this, command, description](Options *o, const std::string &) { + std::cerr << command; + if (positional != Arguments::Zero) std::cerr << ' ' << positionalName; + std::cerr << "\n\n" << description << "\n\nOptions:\n"; + size_t optionWidth = 0; + for (const auto &o : options) + optionWidth = + std::max(optionWidth, o.longName.size() + o.shortName.size()); + // TODO: line-wrap description. + for (const auto &o : options) { + size_t pad = 1 + optionWidth - o.longName.size() - o.shortName.size(); + std::cerr << " " << o.longName << ',' << o.shortName + << std::string(pad, ' ') << o.description << '\n'; + } + std::cerr << '\n'; + exit(EXIT_SUCCESS); + }); + add("--debug", "-d", "Print debug information to stderr", Arguments::Zero, + [](Options *o, const std::string &) { o->debug = true; }); } -bool startsWith(const char *str, const char *prefix) { - return strncmp(str, prefix, strlen(prefix)) == 0; + +Options::~Options() {} + +Options &Options::add(const std::string &longName, const std::string &shortName, + const std::string &description, Arguments arguments, + const Action &action) { + options.push_back({longName, shortName, description, arguments, action, 0}); + return *this; } -} // anonymous namespace -void wasm::processCommandLine(int argc, const char *argv[], Options *options, - const char *help) { +Options &Options::add_positional(const std::string &name, Arguments arguments, + const Action &action) { + positional = arguments; + positionalName = name; + positionalAction = action; + return *this; +} + +void Options::parse(int argc, const char *argv[]) { assert(argc > 0 && "expect at least program name as an argument"); + size_t positionalsSeen = 0; + auto dashes = [](const std::string &s) { + for (size_t i = 0;; ++i) + if (s[i] != '-') return i; + }; for (size_t i = 1, e = argc; i != e; ++i) { - if (optionIs(argv[i], "--help", "-h")) { - std::cerr << help; - exit(EXIT_SUCCESS); - } else if (optionIs(argv[i], "--debug", "-d")) { - options->debug = true; - } else if (optionIs(argv[i], "--output", "-o")) { - if (i + 1 == e) { - std::cerr << "No output file" << std::endl; - exit(EXIT_FAILURE); - } - if (options->outfile.size()) { - std::cerr << "Expected only one output file, got '" << options->outfile - << "' and '" << argv[i] << "'" << std::endl; - exit(EXIT_FAILURE); - } - options->outfile = argv[++i]; - } else if (startsWith(argv[i], "--")) { - size_t j = 2; - std::string name; - while (argv[i][j] && argv[i][j] != '=') { - name += argv[i][j]; - j++; - } - options->extra[name] = argv[i][j] == '=' ? &argv[i][j + 1] : "(no value)"; - } else { - if (options->infile.size()) { - std::cerr << "Expected only one input file, got '" << options->infile - << "' and '" << argv[i] << "'" << std::endl; - exit(EXIT_FAILURE); + std::string currentOption = argv[i]; + + if (dashes(currentOption) == 0) { + // Positional. + switch (positional) { + case Arguments::Zero: + std::cerr << "Unexpected positional argument '" << currentOption + << "'\n"; + exit(EXIT_FAILURE); + case Arguments::One: + if (positionalsSeen) { + std::cerr << "Unexpected second positional argument '" + << currentOption << "' for " << positionalName << '\n'; + exit(EXIT_FAILURE); + } + // Fallthrough. + case Arguments::N: + positionalAction(this, currentOption); + ++positionalsSeen; } - options->infile = argv[i]; + continue; + } + + // Non-positional. + std::string argument; + auto equal = currentOption.find_first_of('='); + if (equal != std::string::npos) { + argument = currentOption.substr(equal + 1); + currentOption = currentOption.substr(0, equal - 1); + } + Option *option = nullptr; + for (auto &o : options) + if (o.longName == currentOption || o.shortName == currentOption) + option = &o; + if (!option) { + std::cerr << "Unknown option '" << currentOption << "'\n"; + exit(EXIT_FAILURE); + } + switch (option->arguments) { + case Arguments::Zero: + if (argument.size()) { + std::cerr << "Unexpected argument '" << argument + << "' for option '" << currentOption << "'\n"; + exit(EXIT_FAILURE); + } + break; + case Arguments::One: + if (option->seen) { + std::cerr << "Unexpected second argument '" << argument << "' for '" + << currentOption << "'\n"; + exit(EXIT_FAILURE); + } + // Fallthrough. + case Arguments::N: + if (!argument.size()) { + if (i + 1 == e) { + std::cerr << "Couldn't find expected argument for '" << currentOption << "'\n"; + exit(EXIT_FAILURE); + } + argument = argv[++i]; + } } + option->action(this, argument); + ++option->seen; } } -- cgit v1.2.3 From ec87a4d54110965a2145349c697764a06db6bf21 Mon Sep 17 00:00:00 2001 From: JF Bastien Date: Mon, 4 Jan 2016 10:46:13 -0800 Subject: Add curlies. --- src/s2wasm-main.cpp | 3 ++- src/support/command-line.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src/support/command-line.cpp') diff --git a/src/s2wasm-main.cpp b/src/s2wasm-main.cpp index 35e80f916..a7060dd6e 100644 --- a/src/s2wasm-main.cpp +++ b/src/s2wasm-main.cpp @@ -64,8 +64,9 @@ int main(int argc, const char *argv[]) { std::streambuf *buffer; std::ofstream outfile; if (options.extra["output"].size()) { - if (options.debug) + if (options.debug) { std::cerr << "Opening '" << options.extra["output"] << std::endl; + } outfile.open(options.extra["output"], std::ofstream::out | std::ofstream::trunc); if (!outfile.is_open()) { diff --git a/src/support/command-line.cpp b/src/support/command-line.cpp index 9b844ff1e..40e11f60a 100644 --- a/src/support/command-line.cpp +++ b/src/support/command-line.cpp @@ -26,9 +26,10 @@ Options::Options(const std::string &command, const std::string &description) if (positional != Arguments::Zero) std::cerr << ' ' << positionalName; std::cerr << "\n\n" << description << "\n\nOptions:\n"; size_t optionWidth = 0; - for (const auto &o : options) + for (const auto &o : options) { optionWidth = std::max(optionWidth, o.longName.size() + o.shortName.size()); + } // TODO: line-wrap description. for (const auto &o : options) { size_t pad = 1 + optionWidth - o.longName.size() - o.shortName.size(); @@ -63,8 +64,9 @@ void Options::parse(int argc, const char *argv[]) { assert(argc > 0 && "expect at least program name as an argument"); size_t positionalsSeen = 0; auto dashes = [](const std::string &s) { - for (size_t i = 0;; ++i) + for (size_t i = 0;; ++i) { if (s[i] != '-') return i; + } }; for (size_t i = 1, e = argc; i != e; ++i) { std::string currentOption = argv[i]; -- cgit v1.2.3