| /* |
| * 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 "src/option-parser.h" |
| |
| #include <cstdarg> |
| #include <cstdio> |
| #include <cstring> |
| |
| #include "config.h" |
| |
| #if HAVE_ALLOCA |
| #include <alloca.h> |
| #endif |
| |
| 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", CMAKE_PROJECT_VERSION); |
| 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 |