| // 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. |
| |
| #ifndef SRC_DAWN_UTILS_COMMANDLINEPARSER_H_ |
| #define SRC_DAWN_UTILS_COMMANDLINEPARSER_H_ |
| |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/types/span.h" // TODO(343500108): Use std::span when we have C++20. |
| #include "dawn/common/Assert.h" |
| #include "dawn/common/NonMovable.h" |
| |
| namespace dawn::utils { |
| |
| // A helper class to parse command line arguments. |
| // |
| // CommandLineParser parser; |
| // auto& dryRun = parser.AddBool("dry-run", "fake operations").ShortName('d'); |
| // auto& input = parser.AddString("input", "the input file to process").ShortName('i'); |
| // auto& help = opts.AddHelp(); |
| // |
| // auto result = parser.Parse(argc, argv); |
| // if (!result.success) { |
| // std::cerr << result.errorMessage << "\n"; |
| // return 1; |
| // } |
| // |
| // if (help.GetValue()) { |
| // std::cout << "Usage: " << argv[0] << " <options>\n\noptions\n"; |
| // parser.PrintHelp(std::cout); |
| // return 0; |
| // } |
| // |
| // if (dryRun.GetValue() && input.IsSet()) { |
| // doStuffWith(input.GetValue()); |
| // } |
| // // ... |
| // |
| // Command line options can use short-form for boolean options "(-f") and use both spaces or = to |
| // separate the value for an option ("-f=foo" and "-f foo"). |
| |
| // TODO(42241992): Considering supporting more types of options and command line parsing niceties. |
| // - Support "-" with a bunch of short names (like grep -rniI) |
| // - Support returning the part that hasn't been parsed at the end so that users can do something |
| // with it. |
| // - Support "--" being used to separate remaining args. |
| // - Support setting a default to show it in the help. |
| |
| class CommandLineParser { |
| public: |
| // The base class for all options to let them interact with the parser. |
| class OptionBase : NonMovable { |
| public: |
| OptionBase(std::string_view name, std::string_view desc); |
| virtual ~OptionBase(); |
| |
| const std::string& GetName() const; |
| std::string GetShortName() const; |
| const std::string& GetDescription() const; |
| virtual std::string GetParameter() const; |
| // Returns whether parser saw that option in the command line. |
| bool IsSet() const; |
| |
| struct ParseResult { |
| bool success; |
| absl::Span<const std::string_view> remainingArgs = {}; |
| std::string errorMessage = {}; |
| }; |
| ParseResult Parse(absl::Span<const std::string_view> args); |
| |
| protected: |
| virtual ParseResult ParseImpl(absl::Span<const std::string_view> args) = 0; |
| |
| bool mSet = false; |
| std::string mName; |
| std::string mShortName; |
| std::string mDescription; |
| std::string mParameter; |
| }; |
| |
| // A CRTP wrapper around OptionBase to support the fluent setters better. |
| template <typename Child> |
| class Option : public OptionBase { |
| public: |
| using OptionBase::OptionBase; |
| |
| // Adds a short name for the option, like "-f". |
| Child& ShortName(char shortName); |
| // Changes the name of the parameter when generating the --help. |
| Child& Parameter(std::string parameter); |
| }; |
| |
| // An option returning a bool. Defaults to false. |
| // Can be set multiple times on the command line if not using the explicit true/false version. |
| class BoolOption : public Option<BoolOption> { |
| public: |
| BoolOption(std::string_view name, std::string_view desc); |
| ~BoolOption() override; |
| |
| bool GetValue() const; |
| std::string GetParameter() const override; |
| |
| private: |
| ParseResult ParseImpl(absl::Span<const std::string_view> args) override; |
| bool mValue = false; |
| }; |
| BoolOption& AddBool(std::string_view name, std::string_view desc = {}); |
| |
| // An option returning a string. Defaults to the empty string. |
| class StringOption : public Option<StringOption> { |
| public: |
| StringOption(std::string_view name, std::string_view desc); |
| ~StringOption() override; |
| |
| std::string GetValue() const; |
| |
| private: |
| ParseResult ParseImpl(absl::Span<const std::string_view> args) override; |
| std::string mValue; |
| }; |
| StringOption& AddString(std::string_view name, std::string_view desc = {}); |
| |
| // An option returning a list of string split from a comma-separated argument, or the argument |
| // being set multiple times (or both). Defaults to an empty list. |
| class StringListOption : public Option<StringListOption> { |
| public: |
| StringListOption(std::string_view name, std::string_view desc); |
| ~StringListOption() override; |
| |
| absl::Span<const std::string> GetValue() const; |
| std::vector<std::string> GetOwnedValue() const; |
| |
| private: |
| ParseResult ParseImpl(absl::Span<const std::string_view> args) override; |
| std::vector<std::string> mValue; |
| }; |
| StringListOption& AddStringList(std::string_view name, std::string_view desc = {}); |
| |
| // An option converting a string name to a value. The default value can be set with .Default(). |
| // |
| // parser.AddEnum({{{"a", E::A}, {"b", E::B}}}); |
| template <typename E> |
| class EnumOption : public Option<EnumOption<E>> { |
| public: |
| EnumOption(std::vector<std::pair<std::string_view, E>> conversions, |
| std::string_view name, |
| std::string_view desc); |
| ~EnumOption() override; |
| |
| E GetValue() const; |
| std::string GetParameter() const override; |
| |
| EnumOption<E>& Default(E value); |
| |
| private: |
| std::string JoinNames(std::string_view separator) const; |
| OptionBase::ParseResult ParseImpl(absl::Span<const std::string_view> args) override; |
| E mValue; |
| bool mHasDefault; |
| std::vector<std::pair<std::string_view, E>> mConversions; |
| }; |
| static std::string JoinConversionNames(absl::Span<const std::string_view> names, |
| std::string_view separator); |
| template <typename E> |
| EnumOption<E>& AddEnum(std::vector<std::pair<std::string_view, E>> conversions, |
| std::string_view name, |
| std::string_view desc = {}) { |
| return AddOption(std::make_unique<EnumOption<E>>(std::move(conversions), name, desc)); |
| } |
| |
| // Helper to add a --help option. |
| BoolOption& AddHelp(); |
| |
| // Helper structs for the Parse calls. |
| struct ParseResult { |
| bool success; |
| std::string errorMessage = {}; |
| }; |
| struct ParseOptions { |
| bool unknownIsError = true; |
| }; |
| // TODO(343500108): Use designated initializers when we have C++20. |
| static const ParseOptions kDefaultParseOptions; |
| |
| // Parse the arguments provided and set the options. |
| ParseResult Parse(absl::Span<const std::string_view> args, |
| const ParseOptions& parseOptions = kDefaultParseOptions); |
| |
| // Small wrappers around the previous Parse for ease of use. |
| ParseResult Parse(const std::vector<std::string>& args, |
| const ParseOptions& parseOptions = kDefaultParseOptions); |
| ParseResult Parse(int argc, |
| const char** argv, |
| const ParseOptions& parseOptions = kDefaultParseOptions); |
| |
| // Generate summary of options for a --help and add it to the stream. |
| void PrintHelp(std::ostream& s); |
| |
| private: |
| template <typename T> |
| T& AddOption(std::unique_ptr<T> option) { |
| T& result = *option.get(); |
| mOptions.push_back(std::move(option)); |
| return result; |
| } |
| |
| std::vector<std::unique_ptr<OptionBase>> mOptions; |
| }; |
| |
| // Option<Child> |
| template <typename Child> |
| Child& CommandLineParser::Option<Child>::ShortName(char shortName) { |
| mShortName = shortName; |
| return static_cast<Child&>(*this); |
| } |
| |
| template <typename Child> |
| Child& CommandLineParser::Option<Child>::Parameter(std::string parameter) { |
| mParameter = parameter; |
| return static_cast<Child&>(*this); |
| } |
| |
| // EnumOption<E> |
| template <typename E> |
| CommandLineParser::EnumOption<E>::EnumOption( |
| std::vector<std::pair<std::string_view, E>> conversions, |
| std::string_view name, |
| std::string_view desc) |
| : Option<EnumOption<E>>(name, desc), mConversions(conversions) {} |
| |
| template <typename E> |
| CommandLineParser::EnumOption<E>::~EnumOption<E>() = default; |
| |
| template <typename E> |
| E CommandLineParser::EnumOption<E>::GetValue() const { |
| DAWN_ASSERT(this->IsSet() || mHasDefault); |
| return mValue; |
| } |
| |
| template <typename E> |
| CommandLineParser::OptionBase::ParseResult CommandLineParser::EnumOption<E>::ParseImpl( |
| absl::Span<const std::string_view> args) { |
| if (this->IsSet()) { |
| return {false, args, "cannot be set multiple times"}; |
| } |
| |
| if (args.empty()) { |
| return {false, args, "expected a value"}; |
| } |
| |
| for (auto conversion : mConversions) { |
| if (conversion.first == args.front()) { |
| mValue = conversion.second; |
| return {true, args.subspan(1)}; |
| } |
| } |
| |
| // Do manual string sums to avoid include absl_format in this header. |
| return {false, args, |
| "unknown value \"" + std::string(args.front()) + "\". Expected one of " + |
| JoinNames(", ") + "."}; |
| } |
| |
| template <typename E> |
| std::string CommandLineParser::EnumOption<E>::GetParameter() const { |
| return JoinNames("|"); |
| } |
| |
| template <typename E> |
| CommandLineParser::EnumOption<E>& CommandLineParser::EnumOption<E>::Default(E value) { |
| mValue = value; |
| mHasDefault = true; |
| return *this; |
| } |
| |
| template <typename E> |
| std::string CommandLineParser::EnumOption<E>::JoinNames(std::string_view separator) const { |
| std::vector<std::string_view> names; |
| for (auto conversion : mConversions) { |
| names.push_back(conversion.first); |
| } |
| return JoinConversionNames(names, separator); |
| } |
| |
| } // namespace dawn::utils |
| |
| #endif // SRC_DAWN_UTILS_COMMANDLINEPARSER_H_ |