Source code

Revision control

Copy as Markdown

Other Tools

/*
* Copyright 2016 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wabt/option-parser.h"
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include "wabt/config.h"
namespace wabt {
OptionParser::Option::Option(char short_name,
const std::string& long_name,
const std::string& metavar,
HasArgument has_argument,
const std::string& help,
const Callback& callback)
: short_name(short_name),
long_name(long_name),
metavar(metavar),
has_argument(has_argument == HasArgument::Yes),
help(help),
callback(callback) {}
OptionParser::Argument::Argument(const std::string& name,
ArgumentCount count,
const Callback& callback)
: name(name), count(count), callback(callback) {}
OptionParser::OptionParser(const char* program_name, const char* description)
: program_name_(program_name),
description_(description),
on_error_([this](const std::string& message) { DefaultError(message); }) {
// Add common options
AddOption("help", "Print this help message", [this]() {
PrintHelp();
exit(0);
});
AddOption("version", "Print version information", []() {
printf("%s\n", WABT_VERSION_STRING);
exit(0);
});
}
void OptionParser::AddOption(const Option& option) {
options_.emplace_back(option);
}
void OptionParser::AddArgument(const std::string& name,
ArgumentCount count,
const Callback& callback) {
arguments_.emplace_back(name, count, callback);
}
void OptionParser::AddOption(char short_name,
const char* long_name,
const char* help,
const NullCallback& callback) {
Option option(short_name, long_name, std::string(), HasArgument::No, help,
[callback](const char*) { callback(); });
AddOption(option);
}
void OptionParser::AddOption(const char* long_name,
const char* help,
const NullCallback& callback) {
Option option('\0', long_name, std::string(), HasArgument::No, help,
[callback](const char*) { callback(); });
AddOption(option);
}
void OptionParser::AddOption(char short_name,
const char* long_name,
const char* metavar,
const char* help,
const Callback& callback) {
Option option(short_name, long_name, metavar, HasArgument::Yes, help,
callback);
AddOption(option);
}
void OptionParser::SetErrorCallback(const Callback& callback) {
on_error_ = callback;
}
// static
int OptionParser::Match(const char* s,
const std::string& full,
bool has_argument) {
int i;
for (i = 0;; i++) {
if (full[i] == '\0') {
// Perfect match. Return +1, so it will be preferred over a longer option
// with the same prefix.
if (s[i] == '\0') {
return i + 1;
}
// We want to fail if s is longer than full, e.g. --foobar vs. --foo.
// However, if s ends with an '=', it's OK.
if (!(has_argument && s[i] == '=')) {
return -1;
}
break;
}
if (s[i] == '\0') {
break;
}
if (s[i] != full[i]) {
return -1;
}
}
return i;
}
void OptionParser::Errorf(const char* format, ...) {
WABT_SNPRINTF_ALLOCA(buffer, length, format);
std::string msg(program_name_);
msg += ": ";
msg += buffer;
msg += "\nTry '--help' for more information.";
on_error_(msg.c_str());
}
void OptionParser::DefaultError(const std::string& message) {
WABT_FATAL("%s\n", message.c_str());
}
void OptionParser::HandleArgument(size_t* arg_index, const char* arg_value) {
if (*arg_index >= arguments_.size()) {
Errorf("unexpected argument '%s'", arg_value);
return;
}
Argument& argument = arguments_[*arg_index];
argument.callback(arg_value);
argument.handled_count++;
if (argument.count == ArgumentCount::One) {
(*arg_index)++;
}
}
void OptionParser::Parse(int argc, char* argv[]) {
size_t arg_index = 0;
bool processing_options = true;
for (int i = 1; i < argc; ++i) {
const char* arg = argv[i];
if (!processing_options || arg[0] != '-') {
// Non-option argument.
HandleArgument(&arg_index, arg);
continue;
}
if (arg[1] == '-') {
if (arg[2] == '\0') {
// -- on its own means stop processing args, everything should
// be treated as positional.
processing_options = false;
continue;
}
// Long option.
int best_index = -1;
int best_length = 0;
int best_count = 0;
for (size_t j = 0; j < options_.size(); ++j) {
const Option& option = options_[j];
if (!option.long_name.empty()) {
int match_length =
Match(&arg[2], option.long_name, option.has_argument);
if (match_length > best_length) {
best_index = j;
best_length = match_length;
best_count = 1;
} else if (match_length == best_length && best_length > 0) {
best_count++;
}
}
}
if (best_count > 1) {
Errorf("ambiguous option '%s'", arg);
continue;
} else if (best_count == 0) {
Errorf("unknown option '%s'", arg);
continue;
}
const Option& best_option = options_[best_index];
const char* option_argument = nullptr;
if (best_option.has_argument) {
if (arg[best_length + 1] != 0 && // This byte is 0 on a full match.
arg[best_length + 2] == '=') { // +2 to skip "--".
option_argument = &arg[best_length + 3];
} else {
if (i + 1 == argc || argv[i + 1][0] == '-') {
Errorf("option '--%s' requires argument",
best_option.long_name.c_str());
continue;
}
++i;
option_argument = argv[i];
}
}
best_option.callback(option_argument);
} else {
// Short option.
if (arg[1] == '\0') {
// Just "-".
HandleArgument(&arg_index, arg);
continue;
}
// Allow short names to be combined, e.g. "-d -v" => "-dv".
for (int k = 1; arg[k]; ++k) {
bool matched = false;
for (const Option& option : options_) {
if (option.short_name && arg[k] == option.short_name) {
const char* option_argument = nullptr;
if (option.has_argument) {
// A short option with a required argument cannot be followed
// by other short options_.
if (arg[k + 1] != '\0') {
Errorf("option '-%c' requires argument", option.short_name);
break;
}
if (i + 1 == argc || argv[i + 1][0] == '-') {
Errorf("option '-%c' requires argument", option.short_name);
break;
}
++i;
option_argument = argv[i];
}
option.callback(option_argument);
matched = true;
break;
}
}
if (!matched) {
Errorf("unknown option '-%c'", arg[k]);
continue;
}
}
}
}
// For now, all arguments must be provided. Check that the last Argument was
// handled at least once.
if (!arguments_.empty() && arguments_.back().handled_count == 0) {
for (size_t i = arg_index; i < arguments_.size(); ++i) {
if (arguments_[i].count != ArgumentCount::ZeroOrMore) {
Errorf("expected %s argument.", arguments_[i].name.c_str());
}
}
}
}
void OptionParser::PrintHelp() {
printf("usage: %s [options]", program_name_.c_str());
for (size_t i = 0; i < arguments_.size(); ++i) {
Argument& argument = arguments_[i];
switch (argument.count) {
case ArgumentCount::One:
printf(" %s", argument.name.c_str());
break;
case ArgumentCount::OneOrMore:
printf(" %s+", argument.name.c_str());
break;
case ArgumentCount::ZeroOrMore:
printf(" [%s]...", argument.name.c_str());
break;
}
}
printf("\n\n");
printf("%s\n", description_.c_str());
printf("options:\n");
const size_t kExtraSpace = 8;
size_t longest_name_length = 0;
for (const Option& option : options_) {
size_t length;
if (!option.long_name.empty()) {
length = option.long_name.size();
if (!option.metavar.empty()) {
// +1 for '='.
length += option.metavar.size() + 1;
}
} else {
continue;
}
if (length > longest_name_length) {
longest_name_length = length;
}
}
for (const Option& option : options_) {
if (!option.short_name && option.long_name.empty()) {
continue;
}
std::string line;
if (option.short_name) {
line += std::string(" -") + option.short_name + ", ";
} else {
line += " ";
}
std::string flag;
if (!option.long_name.empty()) {
flag = "--";
if (!option.metavar.empty()) {
flag += option.long_name + '=' + option.metavar;
} else {
flag += option.long_name;
}
}
// +2 for "--" of the long flag name.
size_t remaining = longest_name_length + kExtraSpace + 2 - flag.size();
line += flag + std::string(remaining, ' ');
if (!option.help.empty()) {
line += option.help;
}
printf("%s\n", line.c_str());
}
}
} // namespace wabt