/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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