| // Copyright 2024 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "dawn/utils/CommandLineParser.h" | |
| #include <algorithm> | |
| #include <tuple> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/str_split.h" | |
| namespace dawn::utils { | |
| // OptionBase | |
| CommandLineParser::OptionBase::OptionBase(std::string_view name, std::string_view desc) | |
| : mName(name), mDescription(desc), mParameter("value") {} | |
| CommandLineParser::OptionBase::~OptionBase() = default; | |
| const std::string& CommandLineParser::OptionBase::GetName() const { | |
| return mName; | |
| } | |
| std::string CommandLineParser::OptionBase::GetShortName() const { | |
| return mShortName; | |
| } | |
| const std::string& CommandLineParser::OptionBase::GetDescription() const { | |
| return mDescription; | |
| } | |
| std::string CommandLineParser::OptionBase::GetParameter() const { | |
| return mParameter; | |
| } | |
| bool CommandLineParser::OptionBase::IsSet() const { | |
| return mSet; | |
| } | |
| CommandLineParser::OptionBase::ParseResult CommandLineParser::OptionBase::Parse( | |
| absl::Span<const std::string_view> args) { | |
| auto result = ParseImpl(args); | |
| if (result.success) { | |
| mSet = true; | |
| } | |
| return result; | |
| } | |
| // BoolOption | |
| CommandLineParser::BoolOption::BoolOption(std::string_view name, std::string_view desc) | |
| : Option(name, desc) {} | |
| CommandLineParser::BoolOption::~BoolOption() = default; | |
| bool CommandLineParser::BoolOption::GetValue() const { | |
| return mValue; | |
| } | |
| std::string CommandLineParser::BoolOption::GetParameter() const { | |
| return {}; | |
| } | |
| CommandLineParser::OptionBase::ParseResult CommandLineParser::BoolOption::ParseImpl( | |
| absl::Span<const std::string_view> args) { | |
| // Explicit true | |
| if (!args.empty() && args.front() == "true") { | |
| if (IsSet()) { | |
| return {false, args, "cannot set multiple times with explicit true/false arguments"}; | |
| } | |
| mValue = true; | |
| return {true, args.subspan(1)}; | |
| } | |
| // Explicit false | |
| if (!args.empty() && args.front() == "false") { | |
| if (IsSet()) { | |
| return {false, args, "cannot set multiple times with explicit true/false arguments"}; | |
| } | |
| mValue = false; | |
| return {true, args.subspan(1)}; | |
| } | |
| // Assuming --option just means to set it to true. Allow setting the option multiple times | |
| // to true with this form. | |
| if (IsSet() && !mValue) { | |
| return {false, args, "cannot be set to both true and false"}; | |
| } | |
| mValue = true; | |
| return {true, args}; | |
| } | |
| // StringOption | |
| CommandLineParser::StringOption::StringOption(std::string_view name, std::string_view desc) | |
| : Option(name, desc) {} | |
| CommandLineParser::StringOption::~StringOption() = default; | |
| std::string CommandLineParser::StringOption::GetValue() const { | |
| return mValue; | |
| } | |
| CommandLineParser::OptionBase::ParseResult CommandLineParser::StringOption::ParseImpl( | |
| absl::Span<const std::string_view> args) { | |
| if (IsSet()) { | |
| return {false, args, "cannot be set multiple times"}; | |
| } | |
| if (args.empty()) { | |
| return {false, args, "expected a value"}; | |
| } | |
| mValue = args.front(); | |
| return {true, args.subspan(1)}; | |
| } | |
| // StringListOption | |
| CommandLineParser::StringListOption::StringListOption(std::string_view name, std::string_view desc) | |
| : Option(name, desc) {} | |
| CommandLineParser::StringListOption::~StringListOption() = default; | |
| absl::Span<const std::string> CommandLineParser::StringListOption::GetValue() const { | |
| return mValue; | |
| } | |
| std::vector<std::string> CommandLineParser::StringListOption::GetOwnedValue() const { | |
| std::vector<std::string> result; | |
| for (auto& v : mValue) { | |
| result.push_back(v); | |
| } | |
| return result; | |
| } | |
| CommandLineParser::OptionBase::ParseResult CommandLineParser::StringListOption::ParseImpl( | |
| absl::Span<const std::string_view> args) { | |
| if (args.empty()) { | |
| return {false, args, "expected a value"}; | |
| } | |
| for (std::string_view s : absl::StrSplit(args.front(), ",")) { | |
| mValue.push_back(std::string{s}); | |
| } | |
| return {true, args.subspan(1)}; | |
| } | |
| // CommandLineParser | |
| CommandLineParser::BoolOption& CommandLineParser::AddBool(std::string_view name, | |
| std::string_view desc) { | |
| return AddOption(std::make_unique<BoolOption>(name, desc)); | |
| } | |
| CommandLineParser::StringOption& CommandLineParser::AddString(std::string_view name, | |
| std::string_view desc) { | |
| return AddOption(std::make_unique<StringOption>(name, desc)); | |
| } | |
| CommandLineParser::StringListOption& CommandLineParser::AddStringList(std::string_view name, | |
| std::string_view desc) { | |
| return AddOption(std::make_unique<StringListOption>(name, desc)); | |
| } | |
| // static | |
| std::string CommandLineParser::JoinConversionNames(absl::Span<const std::string_view> names, | |
| std::string_view separator) { | |
| return absl::StrJoin(names, separator); | |
| } | |
| CommandLineParser::BoolOption& CommandLineParser::AddHelp() { | |
| return AddBool("help", "Shows the help").ShortName('h'); | |
| } | |
| // static | |
| const CommandLineParser::ParseOptions CommandLineParser::kDefaultParseOptions = {}; | |
| CommandLineParser::ParseResult CommandLineParser::Parse(absl::Span<const std::string_view> args, | |
| const ParseOptions& parseOptions) { | |
| // Build the map of name to option. | |
| absl::flat_hash_map<std::string, OptionBase*> nameToOption; | |
| for (auto& option : mOptions) { | |
| if (!nameToOption.emplace(option->GetName(), option.get()).second) { | |
| return {false, | |
| absl::StrFormat("Duplicate options with name \"%s\"", option->GetName())}; | |
| } | |
| if (!option->GetShortName().empty() && | |
| !nameToOption.emplace(option->GetShortName(), option.get()).second) { | |
| return {false, | |
| absl::StrFormat("Duplicate options with name \"%s\"", option->GetShortName())}; | |
| } | |
| } | |
| auto nextArgs = args; | |
| while (!nextArgs.empty()) { | |
| auto arg = nextArgs.front(); | |
| nextArgs = nextArgs.subspan(1); | |
| // Skip or error if it is not an option. | |
| if (arg.empty() || arg[0] != '-') { | |
| if (parseOptions.unknownIsError) { | |
| return {false, absl::StrFormat("Unknown option \"%s\"", arg)}; | |
| } | |
| continue; | |
| } | |
| // Remove starting - or -- | |
| arg = arg.substr(1); | |
| if (!arg.empty() && arg[0] == '-') { | |
| arg = arg.substr(1); | |
| } | |
| // Split at the = if there is one and use the right side as the option's arguments | |
| if (auto equalPos = arg.find('='); equalPos != std::string::npos) { | |
| auto name = arg.substr(0, equalPos); | |
| auto rest = arg.substr(equalPos + 1); | |
| // Skip or error if it is an unknown option. | |
| if (!nameToOption.contains(name)) { | |
| if (parseOptions.unknownIsError) { | |
| return {false, absl::StrFormat("Unknown option \"%s\"", name)}; | |
| } | |
| continue; | |
| } | |
| auto option = nameToOption[name]; | |
| auto optionResult = option->Parse({&rest, 1}); | |
| if (!optionResult.success) { | |
| return {false, absl::StrFormat("Failure while parsing \"%s\": %s", | |
| option->GetName(), optionResult.errorMessage)}; | |
| } | |
| if (!optionResult.remainingArgs.empty()) { | |
| return {false, absl::StrFormat("Argument \"%s\" was not valid for option \"%s\"", | |
| rest, option->GetName())}; | |
| } | |
| continue; | |
| } | |
| // Otherwise make the option greedily parse from the rest of the command line. | |
| // Skip or error if it is an unknown option. | |
| if (!nameToOption.contains(arg)) { | |
| if (parseOptions.unknownIsError) { | |
| return {false, absl::StrFormat("Unknown option \"%s\"", arg)}; | |
| } | |
| continue; | |
| } | |
| // Try to parse the arg. | |
| auto option = nameToOption[arg]; | |
| auto optionResult = option->Parse(nextArgs); | |
| if (!optionResult.success) { | |
| return {false, absl::StrFormat("Failure while parsing \"%s\": %s", option->GetName(), | |
| optionResult.errorMessage)}; | |
| } | |
| nextArgs = optionResult.remainingArgs; | |
| } | |
| return {true}; | |
| } | |
| CommandLineParser::ParseResult CommandLineParser::Parse(const std::vector<std::string>& args, | |
| const ParseOptions& parseOptions) { | |
| std::vector<std::string_view> viewArgs; | |
| for (const auto& arg : args) { | |
| viewArgs.push_back(arg); | |
| } | |
| return Parse(viewArgs, parseOptions); | |
| } | |
| CommandLineParser::ParseResult CommandLineParser::Parse(int argc, | |
| const char** argv, | |
| const ParseOptions& parseOptions) { | |
| std::vector<std::string_view> args; | |
| for (int i = 1; i < argc; i++) { | |
| args.push_back(argv[i]); | |
| } | |
| return Parse(args, parseOptions); | |
| } | |
| void CommandLineParser::PrintHelp(std::ostream& s) { | |
| // Sort options in alphabetical order using a trick that std::tuple is sorted lexicographically. | |
| std::vector<std::tuple<std::string_view, OptionBase*>> sortedOptions; | |
| for (auto& option : mOptions) { | |
| sortedOptions.emplace_back(option->GetName(), option.get()); | |
| } | |
| std::sort(sortedOptions.begin(), sortedOptions.end()); | |
| for (auto& [name, option] : sortedOptions) { | |
| s << "--" << name; | |
| std::string parameter = option->GetParameter(); | |
| if (!parameter.empty()) { | |
| s << " <" << parameter << ">"; | |
| } | |
| const auto& desc = option->GetDescription(); | |
| if (!desc.empty()) { | |
| s << " " << desc; | |
| } | |
| s << "\n"; | |
| if (!option->GetShortName().empty()) { | |
| s << "-" << option->GetShortName() << " alias for --" << name << "\n"; | |
| } | |
| } | |
| } | |
| } // namespace dawn::utils |