| /* | |
| * Copyright 2015 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 "support/command-line.h" | |
| #include "config.h" | |
| #include "support/debug.h" | |
| #include "support/path.h" | |
| #ifdef USE_WSTRING_PATHS | |
| #ifndef NOMINMAX | |
| #define NOMINMAX | |
| #endif | |
| #include <windows.h> | |
| #include <shellapi.h> | |
| #endif | |
| using namespace wasm; | |
| #ifndef SCREEN_WIDTH | |
| #define SCREEN_WIDTH 80 | |
| #endif | |
| void printWrap(std::ostream& os, int leftPad, const std::string& content) { | |
| int len = content.size(); | |
| int space = SCREEN_WIDTH - leftPad; | |
| std::string nextWord; | |
| std::string pad(leftPad, ' '); | |
| for (int i = 0; i <= len; ++i) { | |
| if (i != len && content[i] != ' ' && content[i] != '\n') { | |
| nextWord += content[i]; | |
| } else { | |
| if (static_cast<int>(nextWord.size()) > space) { | |
| os << '\n' << pad; | |
| space = SCREEN_WIDTH - leftPad; | |
| } | |
| os << nextWord; | |
| space -= nextWord.size() + 1; | |
| if (space > 0) { | |
| os << ' '; | |
| } | |
| nextWord.clear(); | |
| if (content[i] == '\n') { | |
| os << '\n'; | |
| space = SCREEN_WIDTH - leftPad; | |
| } | |
| } | |
| } | |
| } | |
| Options::Options(const std::string& command, const std::string& description) | |
| : debug(false), positional(Arguments::Zero) { | |
| std::string GeneralOption = "General options"; | |
| if (getenv("BINARYEN_DEBUG")) { | |
| setDebugEnabled(getenv("BINARYEN_DEBUG")); | |
| } | |
| add("--version", | |
| "", | |
| "Output version information and exit", | |
| GeneralOption, | |
| Arguments::Zero, | |
| [command](Options*, const std::string&) { | |
| std::cout << command << " version " << PROJECT_VERSION << '\n'; | |
| exit(0); | |
| }); | |
| add("--help", | |
| "-h", | |
| "Show this help message and exit", | |
| GeneralOption, | |
| Arguments::Zero, | |
| [this, command, description](Options* o, const std::string&) { | |
| for (size_t i = 0; i < SCREEN_WIDTH; i++) { | |
| std::cout << '='; | |
| } | |
| std::cout << '\n'; | |
| std::cout << command; | |
| if (positional != Arguments::Zero) { | |
| std::cout << ' ' << positionalName; | |
| } | |
| std::cout << "\n\n"; | |
| printWrap(std::cout, 0, description); | |
| std::cout << '\n'; | |
| for (size_t i = 0; i < SCREEN_WIDTH; i++) { | |
| std::cout << '='; | |
| } | |
| std::cout << '\n'; | |
| size_t optionWidth = 0; | |
| for (const auto& o : options) { | |
| if (o.hidden) { | |
| continue; | |
| } | |
| optionWidth = | |
| std::max(optionWidth, o.longName.size() + o.shortName.size()); | |
| } | |
| for (int i = int(categories.size()) - 1; i >= 0; i--) { | |
| auto& category = categories[i]; | |
| std::cout << "\n\n" << category << ":\n"; | |
| for (size_t i = 0; i < category.size() + 1; i++) { | |
| std::cout << '-'; | |
| } | |
| std::cout << '\n'; | |
| for (const auto& o : options) { | |
| if (o.hidden || o.category != category) { | |
| continue; | |
| } | |
| std::cout << '\n'; | |
| bool long_n_short = | |
| o.longName.size() != 0 && o.shortName.size() != 0; | |
| size_t pad = | |
| 1 + optionWidth - o.longName.size() - o.shortName.size(); | |
| std::cout << " " << o.longName << (long_n_short ? ',' : ' ') | |
| << o.shortName << std::string(pad, ' '); | |
| printWrap(std::cout, optionWidth + 4, o.description); | |
| std::cout << '\n'; | |
| } | |
| } | |
| std::cout << '\n'; | |
| exit(EXIT_SUCCESS); | |
| }); | |
| add("--debug", | |
| "-d", | |
| "Print debug information to stderr", | |
| GeneralOption, | |
| Arguments::Optional, | |
| [&](Options* o, const std::string& arguments) { | |
| debug = true; | |
| setDebugEnabled(arguments.c_str()); | |
| }); | |
| } | |
| Options::~Options() {} | |
| Options& Options::add(const std::string& longName, | |
| const std::string& shortName, | |
| const std::string& description, | |
| const std::string& category, | |
| Arguments arguments, | |
| const Action& action, | |
| bool hidden) { | |
| options.push_back( | |
| {longName, shortName, description, category, arguments, action, hidden, 0}); | |
| if (std::find(categories.begin(), categories.end(), category) == | |
| categories.end()) { | |
| categories.push_back(category); | |
| } | |
| return *this; | |
| } | |
| 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[]) { | |
| // On Windows, get the wide char version of the command line flags, and convert | |
| // each one to std::string with UTF-8 manually. This means that all paths in | |
| // Binaryen are stored this way on all platforms right up until a library call | |
| // is made to open a file (at which point we use Path::to_path to convert back) | |
| // so that it works with the underlying Win32 APIs. | |
| // Only argList (and not argv) should be used below. | |
| #ifdef USE_WSTRING_PATHS | |
| LPWSTR* argListW = CommandLineToArgvW(GetCommandLineW(), &argc); | |
| std::vector<std::string> argList; | |
| for (size_t i = 0, e = argc; i < e; ++i) { | |
| argList.push_back(wasm::Path::wstring_to_string(argListW[i])); | |
| } | |
| #else | |
| const char** argList = argv; | |
| #endif | |
| 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 < s.size(); ++i) { | |
| if (s[i] != '-') { | |
| return i; | |
| } | |
| } | |
| return s.size(); | |
| }; | |
| for (size_t i = 1, e = argc; i != e; ++i) { | |
| std::string currentOption = argList[i]; | |
| // "-" alone is a positional option | |
| if (dashes(currentOption) == 0 || currentOption == "-") { | |
| // Positional. | |
| switch (positional) { | |
| case Arguments::Zero: | |
| // Optional arguments must use --flag=A format, and not separated by | |
| // spaces (which would be ambiguous). | |
| case Arguments::Optional: | |
| 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; | |
| break; | |
| } | |
| continue; | |
| } | |
| // Non-positional. | |
| std::string argument; | |
| bool explicitArg = false; | |
| auto equal = currentOption.find_first_of('='); | |
| if (equal != std::string::npos) { | |
| argument = currentOption.substr(equal + 1); | |
| currentOption = currentOption.substr(0, equal); | |
| explicitArg = true; | |
| } | |
| 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() && !explicitArg) { | |
| if (i + 1 == e) { | |
| std::cerr << "Couldn't find expected argument for '" | |
| << currentOption << "'\n"; | |
| exit(EXIT_FAILURE); | |
| } | |
| argument = argList[++i]; | |
| } | |
| break; | |
| case Arguments::Optional: | |
| break; | |
| } | |
| option->action(this, argument); | |
| ++option->seen; | |
| } | |
| } |