#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