You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
7.1 KiB
C++
252 lines
7.1 KiB
C++
|
|
#ifndef REVERSI_ARGUMENTPARSER_H
|
|
#define REVERSI_ARGUMENTPARSER_H
|
|
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <iostream>
|
|
#include <unordered_map>
|
|
#include <cassert>
|
|
|
|
namespace cb::argparser {
|
|
|
|
enum class Mode {
|
|
POSITIONAL,
|
|
REQUIRED,
|
|
OPTIONAL,
|
|
};
|
|
|
|
enum class Type {
|
|
STRING,
|
|
BOOL,
|
|
INTEGER,
|
|
};
|
|
|
|
struct ParseArgument {
|
|
std::string shortName;
|
|
std::string name;
|
|
std::string description;
|
|
std::string defaultValue;
|
|
Type type;
|
|
Mode mode;
|
|
|
|
bool isPresent{false};
|
|
};
|
|
|
|
|
|
class Argument {
|
|
private:
|
|
std::string content;
|
|
Type type{Type::STRING};
|
|
public:
|
|
|
|
Argument() = default;
|
|
|
|
explicit Argument(std::string content, Type type = Type::STRING) : content{std::move(content)}, type{type} {
|
|
if (type == Type::BOOL && this->content == "false") {
|
|
this->content = "";
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] const std::string &asStr() const {
|
|
return content;
|
|
}
|
|
|
|
[[nodiscard]] bool asBool() const {
|
|
return !content.empty();
|
|
}
|
|
|
|
int asInt() {
|
|
assert(type == Type::INTEGER);
|
|
return std::stoi(content);
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
return asBool();
|
|
}
|
|
};
|
|
|
|
|
|
using ArgumentMapType = std::unordered_map<std::string, Argument>;
|
|
|
|
class ArgumentMap : public ArgumentMapType {
|
|
public:
|
|
|
|
Argument &operator[](std::string &&argumentName) {
|
|
assert(count(argumentName) > 0 && "Argument not found!");
|
|
return this->at(argumentName);
|
|
}
|
|
};
|
|
|
|
|
|
class ArgumentParser {
|
|
private:
|
|
std::vector<ParseArgument> arguments;
|
|
std::vector<ParseArgument> positionalArguments;
|
|
|
|
public:
|
|
void addArgument(const std::string &shortName, const std::string &name, Type type, std::string description,
|
|
bool defaultValue, Mode mode = Mode::OPTIONAL) {
|
|
assert(type == Type::BOOL);
|
|
std::string value = defaultValue ? "1" : "";
|
|
addArgument(shortName, name, type, std::move(description), value, mode);
|
|
}
|
|
|
|
void addArgument(const std::string &shortName, const std::string &name, Type type, std::string description,
|
|
const char *defaultValue, Mode mode = Mode::OPTIONAL) {
|
|
addArgument(shortName, name, type, std::move(description), std::string{defaultValue}, mode);
|
|
}
|
|
|
|
void addArgument(const std::string &shortName, const std::string &name, Type type, std::string description,
|
|
int defaultValue, Mode mode = Mode::OPTIONAL) {
|
|
assert(type == Type::INTEGER);
|
|
addArgument(shortName, name, type, std::move(description), std::to_string(defaultValue), mode);
|
|
}
|
|
|
|
void addArgument(const std::string &shortName, const std::string &name, Type type, std::string description,
|
|
std::string defaultValue = "", Mode mode = Mode::OPTIONAL) {
|
|
if (argumentExists(shortName, name)) {
|
|
const auto errorMessage = "ParseArgument already exists --" + name + " -" + shortName;
|
|
throw std::runtime_error(errorMessage);
|
|
}
|
|
|
|
ParseArgument argument{};
|
|
argument.shortName = shortName;
|
|
argument.name = name;
|
|
argument.type = type;
|
|
argument.mode = mode;
|
|
argument.description = std::move(description);
|
|
argument.defaultValue = std::move(defaultValue);
|
|
|
|
if (mode == Mode::POSITIONAL) {
|
|
positionalArguments.push_back(argument);
|
|
} else {
|
|
arguments.push_back(argument);
|
|
}
|
|
}
|
|
|
|
ArgumentMap parse(int argc, char *argv[]) {
|
|
if (argc <= positionalArguments.size()) {
|
|
const auto errorMessage =
|
|
"Not enough arguments provided";
|
|
// TODO print help message
|
|
throw std::runtime_error(errorMessage);
|
|
}
|
|
|
|
for (auto &&arg: arguments) {
|
|
arg.isPresent = false;
|
|
}
|
|
|
|
ArgumentMap parsedArgs = parseArguments(argc, argv);
|
|
|
|
verifyRequiredArgumentsPresent();
|
|
|
|
return parsedArgs;
|
|
}
|
|
|
|
private:
|
|
|
|
static void insertArgument(ArgumentMap &parsedArgs, ParseArgument &positionalArg, const Argument &argument) {
|
|
parsedArgs.insert_or_assign(positionalArg.name, argument);
|
|
if (!positionalArg.shortName.empty()) {
|
|
parsedArgs.insert_or_assign(positionalArg.shortName, argument);
|
|
}
|
|
}
|
|
|
|
ArgumentMap parseArguments(int argc, char *const *argv) {
|
|
ArgumentMap parsedArgs{};
|
|
|
|
// TODO gen help message
|
|
parsedArgs.insert_or_assign("-h", Argument{"Help"});
|
|
parsedArgs.insert_or_assign("--help", Argument{"Help"});
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
bool handled = false;
|
|
|
|
for (auto &&positionalArg: positionalArguments) {
|
|
insertArgument(parsedArgs, positionalArg, Argument{argv[i], positionalArg.type});
|
|
handled = true;
|
|
}
|
|
|
|
if (handled) {
|
|
continue;
|
|
}
|
|
|
|
for (auto &&arg: arguments) {
|
|
if (!matchesArgument(argv[i], arg)) {
|
|
continue;
|
|
}
|
|
|
|
if (requiresAdditionalArgument(arg, argc, argv, i)) {
|
|
auto content = std::string{argv[i + 1]};
|
|
consumeArgument(parsedArgs, arg, std::move(content));
|
|
i++;
|
|
} else {
|
|
consumeArgument(parsedArgs, arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (auto &&arg: arguments) {
|
|
if (parsedArgs.find(arg.name) != parsedArgs.end()) {
|
|
continue; // already in parsed args
|
|
}
|
|
|
|
insertArgument(parsedArgs, arg, Argument{arg.defaultValue, arg.type});
|
|
}
|
|
|
|
return parsedArgs;
|
|
}
|
|
|
|
static bool requiresAdditionalArgument(const ParseArgument &parseArgument, int argc, char *const *argv, int i) {
|
|
if (parseArgument.type != Type::BOOL) {
|
|
return true;
|
|
}
|
|
|
|
if (i + 1 >= argc) {
|
|
return false;
|
|
}
|
|
|
|
if (argv[i + 1][0] == '-') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void consumeArgument(ArgumentMap &parsedArgs, ParseArgument &arg, std::string content = "1") {
|
|
insertArgument(parsedArgs, arg, Argument{std::move(content), arg.type});
|
|
arg.isPresent = true;
|
|
}
|
|
|
|
void verifyRequiredArgumentsPresent() const {
|
|
for (auto &&argument: arguments) {
|
|
if (argument.mode != Mode::REQUIRED) {
|
|
continue;
|
|
}
|
|
if (!argument.isPresent) {
|
|
const auto errorMessage =
|
|
"Required argument not present: --" + argument.name + " -" + argument.shortName;
|
|
throw std::runtime_error(errorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool matchesArgument(const std::string &text, const ParseArgument &arg) {
|
|
return text == arg.shortName || text == arg.name;
|
|
}
|
|
|
|
bool argumentExists(const std::string &shortName, const std::string &name) {
|
|
return std::any_of(arguments.begin(), arguments.end(), [&](ParseArgument &arg) {
|
|
return arg.shortName == shortName || arg.name == name;
|
|
});
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
#endif //REVERSI_ARGUMENTPARSER_H
|