added ArgumentParser
parent
31b668100e
commit
5a8dcf66d4
@ -1,4 +1,13 @@
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
# Export compile command -> better tool integration.
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
|
||||
#######################################################################################################################
|
||||
# Target properties
|
||||
#######################################################################################################################
|
||||
|
||||
#set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
|
||||
#set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
#set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER source/cb.h)
|
||||
|
||||
@ -0,0 +1,251 @@
|
||||
|
||||
#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
|
||||
@ -0,0 +1 @@
|
||||
|
||||
Loading…
Reference in New Issue