blob: c1c3030cbf971c77e2467b635d5fdf2e153ca8f0 [file] [log] [blame]
// 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_