added ArgumentParser

master
cobrapitz 3 years ago
parent 31b668100e
commit 5a8dcf66d4

@ -22,6 +22,7 @@ set(CBLIB_HEADERS
${CBLIB_INCLUDE_DIR}/types/types.h ${CBLIB_INCLUDE_DIR}/types/types.h
${CBLIB_INCLUDE_DIR}/time/time.h ${CBLIB_INCLUDE_DIR}/time/time.h
${CBLIB_INCLUDE_DIR}/test/test.h ${CBLIB_INCLUDE_DIR}/test/test.h
${CBLIB_INCLUDE_DIR}/argparser/argument_parser.h
${CBLIB_INCLUDE_DIR}/string_utils/string_utils.h ${CBLIB_INCLUDE_DIR}/string_utils/string_utils.h
) )
set(CBLIB_SOURCES set(CBLIB_SOURCES
@ -29,6 +30,7 @@ set(CBLIB_SOURCES
${CBLIB_SOURCE_DIR}/types/types.cpp ${CBLIB_SOURCE_DIR}/types/types.cpp
${CBLIB_SOURCE_DIR}/time/time.cpp ${CBLIB_SOURCE_DIR}/time/time.cpp
${CBLIB_SOURCE_DIR}/test/test.cpp ${CBLIB_SOURCE_DIR}/test/test.cpp
${CBLIB_SOURCE_DIR}/argparser/argument_parser.cpp
${CBLIB_SOURCE_DIR}/string_utils/string_utils.cpp ${CBLIB_SOURCE_DIR}/string_utils/string_utils.cpp
) )

@ -1,4 +1,13 @@
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
# Export compile command -> better tool integration. # 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)

@ -21,6 +21,7 @@
#include "cb/test/test.h" #include "cb/test/test.h"
#include "cb/time/time.h" #include "cb/time/time.h"
#include "cb/types/types.h" #include "cb/types/types.h"
#include "cb/argparser/argument_parser.h"
#include "cb/string_utils/string_utils.h" #include "cb/string_utils/string_utils.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

@ -4,7 +4,7 @@
namespace test { namespace cb::test {
} }

Loading…
Cancel
Save