From 5a8dcf66d47b279dc050a362aa31282ce9dbeb85 Mon Sep 17 00:00:00 2001 From: cobrapitz <12397702+cobrapitz@users.noreply.github.com> Date: Sat, 6 May 2023 01:56:48 +0200 Subject: [PATCH] added ArgumentParser --- CMakeLists.txt | 2 + cmake/CMakeSettings.cmake | 11 +- include/cb.h | 1 + include/cb/argparser/argument_parser.h | 251 +++++++++++++++++++++++++ include/cb/test/test.h | 2 +- src/cb/argument_parser.cpp | 1 + 6 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 include/cb/argparser/argument_parser.h create mode 100644 src/cb/argument_parser.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b57bb9b..ca4e4b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set(CBLIB_HEADERS ${CBLIB_INCLUDE_DIR}/types/types.h ${CBLIB_INCLUDE_DIR}/time/time.h ${CBLIB_INCLUDE_DIR}/test/test.h + ${CBLIB_INCLUDE_DIR}/argparser/argument_parser.h ${CBLIB_INCLUDE_DIR}/string_utils/string_utils.h ) set(CBLIB_SOURCES @@ -29,6 +30,7 @@ set(CBLIB_SOURCES ${CBLIB_SOURCE_DIR}/types/types.cpp ${CBLIB_SOURCE_DIR}/time/time.cpp ${CBLIB_SOURCE_DIR}/test/test.cpp + ${CBLIB_SOURCE_DIR}/argparser/argument_parser.cpp ${CBLIB_SOURCE_DIR}/string_utils/string_utils.cpp ) diff --git a/cmake/CMakeSettings.cmake b/cmake/CMakeSettings.cmake index 2ebb9e1..9c6bead 100644 --- a/cmake/CMakeSettings.cmake +++ b/cmake/CMakeSettings.cmake @@ -1,4 +1,13 @@ set(CMAKE_CXX_STANDARD 20) # Export compile command -> better tool integration. -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) \ No newline at end of file +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) diff --git a/include/cb.h b/include/cb.h index 8f9ae31..ef50683 100644 --- a/include/cb.h +++ b/include/cb.h @@ -21,6 +21,7 @@ #include "cb/test/test.h" #include "cb/time/time.h" #include "cb/types/types.h" +#include "cb/argparser/argument_parser.h" #include "cb/string_utils/string_utils.h" diff --git a/include/cb/argparser/argument_parser.h b/include/cb/argparser/argument_parser.h new file mode 100644 index 0000000..4f0b456 --- /dev/null +++ b/include/cb/argparser/argument_parser.h @@ -0,0 +1,251 @@ + +#ifndef REVERSI_ARGUMENTPARSER_H +#define REVERSI_ARGUMENTPARSER_H + +#include +#include +#include +#include +#include +#include +#include + +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; + +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 arguments; + std::vector 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 diff --git a/include/cb/test/test.h b/include/cb/test/test.h index 4bd2ac0..f0530b1 100644 --- a/include/cb/test/test.h +++ b/include/cb/test/test.h @@ -4,7 +4,7 @@ -namespace test { +namespace cb::test { } diff --git a/src/cb/argument_parser.cpp b/src/cb/argument_parser.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/cb/argument_parser.cpp @@ -0,0 +1 @@ +