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