| // 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(absl::string_view name, absl::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 absl::string_view> args) { |
| auto result = ParseImpl(args); |
| if (result.success) { |
| mSet = true; |
| } |
| return result; |
| } |
| |
| // BoolOption |
| |
| CommandLineParser::BoolOption::BoolOption(absl::string_view name, absl::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 absl::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(absl::string_view name, absl::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 absl::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(absl::string_view name, |
| absl::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 absl::string_view> args) { |
| if (args.empty()) { |
| return {false, args, "expected a value"}; |
| } |
| |
| for (absl::string_view s : absl::StrSplit(args.front(), ",")) { |
| mValue.push_back(std::string{s}); |
| } |
| return {true, args.subspan(1)}; |
| } |
| |
| // CommandLineParser |
| |
| CommandLineParser::BoolOption& CommandLineParser::AddBool(absl::string_view name, |
| absl::string_view desc) { |
| return AddOption(std::make_unique<BoolOption>(name, desc)); |
| } |
| |
| CommandLineParser::StringOption& CommandLineParser::AddString(absl::string_view name, |
| absl::string_view desc) { |
| return AddOption(std::make_unique<StringOption>(name, desc)); |
| } |
| |
| CommandLineParser::StringListOption& CommandLineParser::AddStringList(absl::string_view name, |
| absl::string_view desc) { |
| return AddOption(std::make_unique<StringListOption>(name, desc)); |
| } |
| |
| // static |
| std::string CommandLineParser::JoinConversionNames(absl::Span<const absl::string_view> names, |
| absl::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 absl::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<absl::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<absl::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<absl::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 |