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.
CBLib/include/cb/argparser/argument_parser.h

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