/* * 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 #include #include #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