|  | // Copyright 2023 The Tint Authors. | 
|  | // | 
|  | // 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. | 
|  |  | 
|  | #ifndef SRC_TINT_UTILS_CLI_H_ | 
|  | #define SRC_TINT_UTILS_CLI_H_ | 
|  |  | 
|  | #include <deque> | 
|  | #include <optional> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "src/tint/utils/block_allocator.h" | 
|  | #include "src/tint/utils/compiler_macros.h" | 
|  | #include "src/tint/utils/parse_num.h" | 
|  | #include "src/tint/utils/result.h" | 
|  | #include "src/tint/utils/string.h" | 
|  | #include "src/tint/utils/vector.h" | 
|  |  | 
|  | namespace tint::utils::cli { | 
|  |  | 
|  | /// Alias is a fluent-constructor helper for Options | 
|  | struct Alias { | 
|  | /// The alias to apply to an Option | 
|  | std::string value; | 
|  |  | 
|  | /// @param option the option to apply the alias to | 
|  | template <typename T> | 
|  | void Apply(T& option) { | 
|  | option.alias = value; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// ShortName is a fluent-constructor helper for Options | 
|  | struct ShortName { | 
|  | /// The short-name to apply to an Option | 
|  | std::string value; | 
|  |  | 
|  | /// @param option the option to apply the short name to | 
|  | template <typename T> | 
|  | void Apply(T& option) { | 
|  | option.short_name = value; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// Parameter is a fluent-constructor helper for Options | 
|  | struct Parameter { | 
|  | /// The parameter name to apply to an Option | 
|  | std::string value; | 
|  |  | 
|  | /// @param option the option to apply the parameter name to | 
|  | template <typename T> | 
|  | void Apply(T& option) { | 
|  | option.parameter = value; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// Default is a fluent-constructor helper for Options | 
|  | template <typename T> | 
|  | struct Default { | 
|  | /// The default value to apply to an Option | 
|  | T value; | 
|  |  | 
|  | /// @param option the option to apply the default value to | 
|  | template <typename O> | 
|  | void Apply(O& option) { | 
|  | option.default_value = value; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// Deduction guide for Default | 
|  | template <typename T> | 
|  | Default(T) -> Default<T>; | 
|  |  | 
|  | /// Option is the base class for all command line options | 
|  | class Option { | 
|  | public: | 
|  | /// An alias to std::string, used to hold error messages. | 
|  | using Error = std::string; | 
|  |  | 
|  | /// Destructor | 
|  | virtual ~Option(); | 
|  |  | 
|  | /// @return the name of the option, without any leading hyphens. | 
|  | /// Example: 'help' | 
|  | virtual std::string Name() const = 0; | 
|  |  | 
|  | /// @return the alias name of the option, without any leading hyphens. (optional) | 
|  | /// Example: 'flag' | 
|  | virtual std::string Alias() const = 0; | 
|  |  | 
|  | /// @return the shorter name of the option, without any leading hyphens. (optional) | 
|  | /// Example: 'h' | 
|  | virtual std::string ShortName() const = 0; | 
|  |  | 
|  | /// @return a string describing the parameter that the option expects. | 
|  | /// Empty represents no expected parameter. | 
|  | virtual std::string Parameter() const = 0; | 
|  |  | 
|  | /// @return a description of the option. | 
|  | /// Example: 'shows this message' | 
|  | virtual std::string Description() const = 0; | 
|  |  | 
|  | /// @return the default value of the option, or an empty string if there is no default value. | 
|  | virtual std::string DefaultValue() const = 0; | 
|  |  | 
|  | /// Sets the option value to the default (called before arguments are parsed) | 
|  | virtual void SetDefault() = 0; | 
|  |  | 
|  | /// Parses the option's arguments from the list of command line arguments, removing the consumed | 
|  | /// arguments before returning. @p arguments will have already had the option's name consumed | 
|  | /// before calling. | 
|  | /// @param arguments the queue of unprocessed arguments. Parse() may take from the front of @p | 
|  | /// arguments. | 
|  | /// @return empty Error if successfully parsed, otherwise an error string. | 
|  | virtual Error Parse(std::deque<std::string_view>& arguments) = 0; | 
|  |  | 
|  | protected: | 
|  | /// An empty string, used to represent no-error. | 
|  | static constexpr const char* Success = ""; | 
|  |  | 
|  | /// @param expected the expected value(s) for the option | 
|  | /// @return an Error message for a missing argument | 
|  | Error ErrMissingArgument(std::string expected) const { | 
|  | Error err = "missing value for option '--" + Name() + "'"; | 
|  | if (!expected.empty()) { | 
|  | err += "Expected: " + expected; | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /// @param got the argument value provided | 
|  | /// @param reason the reason the argument is invalid (optional) | 
|  | /// @return an Error message for an invalid argument | 
|  | Error ErrInvalidArgument(std::string_view got, std::string reason) const { | 
|  | Error err = "invalid value '" + std::string(got) + "' for option '--" + Name() + "'"; | 
|  | if (!reason.empty()) { | 
|  | err += "\n" + reason; | 
|  | } | 
|  | return err; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// OptionSet is a set of Options, which can parse the command line arguments. | 
|  | class OptionSet { | 
|  | public: | 
|  | /// Unconsumed is a list of unconsumed command line arguments | 
|  | using Unconsumed = utils::Vector<std::string_view, 8>; | 
|  |  | 
|  | /// Constructs and returns a new Option to be owned by the OptionSet | 
|  | /// @tparam T the Option type | 
|  | /// @tparam ARGS the constructor argument types | 
|  | /// @param args the constructor arguments | 
|  | /// @return the constructed Option | 
|  | template <typename T, typename... ARGS> | 
|  | T& Add(ARGS&&... args) { | 
|  | return *options.Create<T>(std::forward<ARGS>(args)...); | 
|  | } | 
|  |  | 
|  | /// Prints to @p out the description of all the command line options. | 
|  | /// @param out the output stream | 
|  | void ShowHelp(std::ostream& out); | 
|  |  | 
|  | /// Parses all the options in @p options. | 
|  | /// @param err the error stream | 
|  | /// @param arguments the command line arguments, excluding the initial executable name | 
|  | /// @return a Result holding a list of arguments that were not consumed as options | 
|  | Result<Unconsumed> Parse(std::ostream& err, utils::VectorRef<std::string_view> arguments); | 
|  |  | 
|  | private: | 
|  | /// The list of options to parse | 
|  | utils::BlockAllocator<Option, 1024> options; | 
|  | }; | 
|  |  | 
|  | /// ValueOption is an option that accepts a single value | 
|  | template <typename T> | 
|  | class ValueOption : public Option { | 
|  | static constexpr bool is_bool = std::is_same_v<T, bool>; | 
|  | static constexpr bool is_number = | 
|  | !is_bool && (std::is_integral_v<T> || std::is_floating_point_v<T>); | 
|  | static constexpr bool is_string = std::is_same_v<T, std::string>; | 
|  | static_assert(is_bool || is_number || is_string, "unsupported data type"); | 
|  |  | 
|  | public: | 
|  | /// The name of the option, without any leading hyphens. | 
|  | std::string name; | 
|  | /// The alias name of the option, without any leading hyphens. | 
|  | std::string alias; | 
|  | /// The shorter name of the option, without any leading hyphens. | 
|  | std::string short_name; | 
|  | /// A description of the option. | 
|  | std::string description; | 
|  | /// The default value. | 
|  | std::optional<T> default_value; | 
|  | /// The option value. Populated with Parse(). | 
|  | std::optional<T> value; | 
|  | /// A string describing the name of the option's value. | 
|  | std::string parameter = "value"; | 
|  |  | 
|  | /// Constructor | 
|  | ValueOption() = default; | 
|  |  | 
|  | /// Constructor | 
|  | /// @param option_name the option name | 
|  | /// @param option_description the option description | 
|  | /// @param settings a number of fluent-constructor values that configure the option | 
|  | /// @see ShortName, Parameter, Default | 
|  | template <typename... SETTINGS> | 
|  | ValueOption(std::string option_name, std::string option_description, SETTINGS&&... settings) | 
|  | : name(std::move(option_name)), description(std::move(option_description)) { | 
|  | (settings.Apply(*this), ...); | 
|  | } | 
|  |  | 
|  | std::string Name() const override { return name; } | 
|  |  | 
|  | std::string Alias() const override { return alias; } | 
|  |  | 
|  | std::string ShortName() const override { return short_name; } | 
|  |  | 
|  | std::string Parameter() const override { return parameter; } | 
|  |  | 
|  | std::string Description() const override { return description; } | 
|  |  | 
|  | std::string DefaultValue() const override { | 
|  | return default_value.has_value() ? ToString(*default_value) : ""; | 
|  | } | 
|  |  | 
|  | void SetDefault() override { value = default_value; } | 
|  |  | 
|  | Error Parse(std::deque<std::string_view>& arguments) override { | 
|  | TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE); | 
|  |  | 
|  | if (arguments.empty()) { | 
|  | if constexpr (is_bool) { | 
|  | // Treat as flag (--blah) | 
|  | value = true; | 
|  | return Success; | 
|  | } else { | 
|  | return ErrMissingArgument(parameter); | 
|  | } | 
|  | } | 
|  |  | 
|  | auto arg = arguments.front(); | 
|  |  | 
|  | if constexpr (is_number) { | 
|  | auto result = ParseNumber<T>(arg); | 
|  | if (result) { | 
|  | value = result.Get(); | 
|  | arguments.pop_front(); | 
|  | return Success; | 
|  | } | 
|  | if (result.Failure() == ParseNumberError::kResultOutOfRange) { | 
|  | return ErrInvalidArgument(arg, "value out of range"); | 
|  | } | 
|  | return ErrInvalidArgument(arg, "failed to parse value"); | 
|  | } else if constexpr (is_string) { | 
|  | value = arg; | 
|  | arguments.pop_front(); | 
|  | return Success; | 
|  | } else if constexpr (is_bool) { | 
|  | if (arg == "true") { | 
|  | value = true; | 
|  | arguments.pop_front(); | 
|  | return Success; | 
|  | } | 
|  | if (arg == "false") { | 
|  | value = false; | 
|  | arguments.pop_front(); | 
|  | return Success; | 
|  | } | 
|  | // Next argument is assumed to be another option, or unconsumed argument. | 
|  | // Treat as flag (--blah) | 
|  | value = true; | 
|  | return Success; | 
|  | } | 
|  |  | 
|  | TINT_END_DISABLE_WARNING(UNREACHABLE_CODE); | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// BoolOption is an alias to ValueOption<bool> | 
|  | using BoolOption = ValueOption<bool>; | 
|  |  | 
|  | /// StringOption is an alias to ValueOption<std::string> | 
|  | using StringOption = ValueOption<std::string>; | 
|  |  | 
|  | /// EnumName is a pair of enum value and name. | 
|  | /// @tparam ENUM the enum type | 
|  | template <typename ENUM> | 
|  | struct EnumName { | 
|  | /// Constructor | 
|  | EnumName() = default; | 
|  |  | 
|  | /// Constructor | 
|  | /// @param v the enum value | 
|  | /// @param n the name of the enum value | 
|  | EnumName(ENUM v, std::string n) : value(v), name(std::move(n)) {} | 
|  |  | 
|  | /// the enum value | 
|  | ENUM value; | 
|  | /// the name of the enum value | 
|  | std::string name; | 
|  | }; | 
|  |  | 
|  | /// Deduction guide for EnumName | 
|  | template <typename ENUM> | 
|  | EnumName(ENUM, std::string) -> EnumName<ENUM>; | 
|  |  | 
|  | /// EnumOption is an option that accepts an enumerator of values | 
|  | template <typename ENUM> | 
|  | class EnumOption : public Option { | 
|  | public: | 
|  | /// The name of the option, without any leading hyphens. | 
|  | std::string name; | 
|  | /// The alias name of the option, without any leading hyphens. | 
|  | std::string alias; | 
|  | /// The shorter name of the option, without any leading hyphens. | 
|  | std::string short_name; | 
|  | /// A description of the option. | 
|  | std::string description; | 
|  | /// The enum options as a pair of enum value to name | 
|  | utils::Vector<EnumName<ENUM>, 8> enum_names; | 
|  | /// The default value. | 
|  | std::optional<ENUM> default_value; | 
|  | /// The option value. Populated with Parse(). | 
|  | std::optional<ENUM> value; | 
|  |  | 
|  | /// Constructor | 
|  | EnumOption() = default; | 
|  |  | 
|  | /// Constructor | 
|  | /// @param option_name the option name | 
|  | /// @param option_description the option description | 
|  | /// @param names The enum options as a pair of enum value to name | 
|  | /// @param settings a number of fluent-constructor values that configure the option | 
|  | /// @see ShortName, Parameter, Default | 
|  | template <typename... SETTINGS> | 
|  | EnumOption(std::string option_name, | 
|  | std::string option_description, | 
|  | utils::VectorRef<EnumName<ENUM>> names, | 
|  | SETTINGS&&... settings) | 
|  | : name(std::move(option_name)), | 
|  | description(std::move(option_description)), | 
|  | enum_names(std::move(names)) { | 
|  | (settings.Apply(*this), ...); | 
|  | } | 
|  |  | 
|  | std::string Name() const override { return name; } | 
|  |  | 
|  | std::string ShortName() const override { return short_name; } | 
|  |  | 
|  | std::string Alias() const override { return alias; } | 
|  |  | 
|  | std::string Parameter() const override { return PossibleValues("|"); } | 
|  |  | 
|  | std::string Description() const override { return description; } | 
|  |  | 
|  | std::string DefaultValue() const override { | 
|  | for (auto& enum_name : enum_names) { | 
|  | if (enum_name.value == default_value) { | 
|  | return enum_name.name; | 
|  | } | 
|  | } | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | void SetDefault() override { value = default_value; } | 
|  |  | 
|  | Error Parse(std::deque<std::string_view>& arguments) override { | 
|  | if (arguments.empty()) { | 
|  | return ErrMissingArgument("one of: " + PossibleValues(", ")); | 
|  | } | 
|  | auto& arg = arguments.front(); | 
|  | for (auto& enum_name : enum_names) { | 
|  | if (enum_name.name == arg) { | 
|  | value = enum_name.value; | 
|  | arguments.pop_front(); | 
|  | return Success; | 
|  | } | 
|  | } | 
|  | return ErrInvalidArgument(arg, "Must be one of: " + PossibleValues(", ")); | 
|  | } | 
|  |  | 
|  | /// @param delimiter the delimiter between each enum option | 
|  | /// @returns the accepted enum names delimited with @p delimiter | 
|  | std::string PossibleValues(std::string delimiter) const { | 
|  | std::string out; | 
|  | for (auto& enum_name : enum_names) { | 
|  | if (!out.empty()) { | 
|  | out += delimiter; | 
|  | } | 
|  | out += enum_name.name; | 
|  | } | 
|  | return out; | 
|  | } | 
|  | }; | 
|  |  | 
|  | }  // namespace tint::utils::cli | 
|  |  | 
|  | #endif  // SRC_TINT_UTILS_CLI_H_ |