From a0492df393e848c5ad3f203b96bd0aa6090bb1bd Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 1 Aug 2023 13:43:00 -0400 Subject: [PATCH] args --- include/blt/parse/argparse.h | 156 ++++++++++++++++++++++++----------- src/blt/parse/argparse.cpp | 151 ++++++++++++++++++++++++--------- 2 files changed, 219 insertions(+), 88 deletions(-) diff --git a/include/blt/parse/argparse.h b/include/blt/parse/argparse.h index 3ba77ec..152c443 100644 --- a/include/blt/parse/argparse.h +++ b/include/blt/parse/argparse.h @@ -12,10 +12,11 @@ #include #include #include +#include namespace blt::parser { - enum class arg_action { + enum class arg_action_t { STORE, STORE_CONST, STORE_TRUE, @@ -28,14 +29,13 @@ namespace blt::parser { EXTEND }; - class arg_t { - private: - - public: - + enum class arg_result_t { + BOOL, + VALUE, + VECTOR }; - class arg_vector { + class arg_vector_t { private: std::vector names; std::vector flags; @@ -43,23 +43,23 @@ namespace blt::parser { void insertAndSort(const std::string& str); public: - arg_vector() = default; + arg_vector_t() = default; - arg_vector(const std::vector& args); + arg_vector_t(const std::vector& args); - arg_vector(std::initializer_list args); + arg_vector_t(std::initializer_list args); - arg_vector(const std::string& arg); + arg_vector_t(const std::string& arg); - arg_vector(const char* arg); + arg_vector_t(const char* arg); - arg_vector& operator=(const std::string& arg); + arg_vector_t& operator=(const std::string& arg); - arg_vector& operator=(const char* arg); + arg_vector_t& operator=(const char* arg); - arg_vector& operator=(std::initializer_list& args); + arg_vector_t& operator=(std::initializer_list& args); - arg_vector& operator=(std::vector& args); + arg_vector_t& operator=(std::vector& args); [[nodiscard]] inline std::vector& getNames() { return names; @@ -76,44 +76,84 @@ namespace blt::parser { [[nodiscard]] inline const std::vector& getFlags() const { return flags; } + + struct equals { + bool operator()(const arg_vector_t& a1, const arg_vector_t& a2) const { + // arg cannot have both + if (!a1.names.empty()) { + // match all pos arg + return std::ranges::all_of(a1.names, [&a2](const auto& n) -> bool { + if (std::find(a2.names.begin(), a2.names.end(), n) == a2.names.end()) + return false; + return true; + }); + } else { + // match any flag (--foo or -f) + return std::ranges::all_of(a1.flags, [&a2](const auto& f) -> bool { + if (std::find(a2.flags.begin(), a2.flags.end(), f) != a2.flags.end()) + return true; + return false; + }); + } + } + }; + + struct hash { + size_t operator()(const arg_vector_t& a) const { + size_t v = 0; + std::hash hash; + for (const auto& n : a.names) { + v >>= 8; + v += hash(n); + } + for (const auto& f : a.flags) { + v >>= 8; + v += hash(f); + } + return v; + } + }; }; - class arg_nargs { + class arg_nargs_t { private: + friend class argparse; static constexpr int UNKNOWN = 0x1; static constexpr int ALL = 0x2; static constexpr int ALL_REQUIRED = 0x4; + // 0 means default behaviour (consume 1 if presented otherwise ignore) int args = 0; + // 0 indicates args is used int flags = 0; void decode(char c); public: - arg_nargs() = default; + arg_nargs_t() = default; - arg_nargs(int args): args(args) {} + arg_nargs_t(int args): args(args) {} - arg_nargs(char c); + arg_nargs_t(char c); - arg_nargs(std::string s); + arg_nargs_t(std::string s); - arg_nargs(const char* s); + arg_nargs_t(const char* s); - arg_nargs& operator=(const std::string& s); + arg_nargs_t& operator=(const std::string& s); - arg_nargs& operator=(const char* s); + arg_nargs_t& operator=(const char* s); - arg_nargs& operator=(char c); + arg_nargs_t& operator=(char c); - arg_nargs& operator=(int args); + arg_nargs_t& operator=(int args); }; - struct arg_properties { + struct arg_properties_t { private: public: - arg_vector a_flags; - arg_action a_action = arg_action::STORE; - arg_nargs a_nargs = 0; + arg_vector_t a_flags; + arg_action_t a_action = arg_action_t::STORE; + arg_nargs_t a_nargs = 0; std::optional a_const{}; std::string a_default{}; std::string a_def{}; @@ -122,22 +162,24 @@ namespace blt::parser { bool a_required = false; }; - class arg_tokenizer { + class arg_tokenizer_t { private: static constexpr char FLAG = '-'; std::vector args; size_t nextIndex = 0; - inline const std::string& get(size_t i){ + inline const std::string& get(size_t i) { return args[i]; } - inline bool hasNext(size_t i){ - return (size_t)i < args.size(); - } - public: - arg_tokenizer() = default; - arg_tokenizer(size_t argc, const char** argv); + inline bool hasNext(size_t i) { + return (size_t) i < args.size(); + } + + public: + arg_tokenizer_t() = default; + + arg_tokenizer_t(size_t argc, const char** argv); inline void forward() { nextIndex++; @@ -155,7 +197,7 @@ namespace blt::parser { return hasNext(nextIndex); } - inline bool isFlag(size_t i){ + inline bool isFlag(size_t i) { return get(i).starts_with(FLAG); } @@ -165,27 +207,41 @@ namespace blt::parser { }; class argparse { + public: + typedef std::variant> arg_data_t; private: - arg_tokenizer tokenizer; - struct { - std::vector argStorage; - HASHMAP flagAssociations; - std::vector> nameAssociations; + friend argparse; + private: + std::vector arg_storage; + public: + std::vector> name_associations; + HASHMAP flag_associations; + HASHSET required_vars; } user_args; struct arg_results { - std::string programName; - HASHMAP positionalArgs; - + friend argparse; + private: + HASHSET found_required; + public: + std::string program_name; + HASHMAP positional_args; + HASHMAP flag_args; } loaded_args; - - static bool validateArgument(const arg_properties& args); + private: + static bool validateArgument(const arg_properties_t& args); + bool consumeFlagArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, arg_data_t& arg_data); + void handlePositionalArgument(arg_tokenizer_t& arg_tokenizer, size_t& last_pos); + void handleFlagArgument(arg_tokenizer_t& arg_tokenizer); public: argparse() = default; - void addArgument(const arg_properties& args); + void addArgument(const arg_properties_t& args); + const arg_results& parse_args(int argc, const char** argv); + + void printHelp(); }; } diff --git a/src/blt/parse/argparse.cpp b/src/blt/parse/argparse.cpp index 466a5df..24e5e2a 100644 --- a/src/blt/parse/argparse.cpp +++ b/src/blt/parse/argparse.cpp @@ -8,140 +8,215 @@ namespace blt::parser { - arg_vector::arg_vector(const std::vector& args) { + arg_vector_t::arg_vector_t(const std::vector& args) { for (auto& arg : args) insertAndSort(arg); } - arg_vector::arg_vector(std::initializer_list args) { + arg_vector_t::arg_vector_t(std::initializer_list args) { for (auto& arg : args) insertAndSort(arg); } - arg_vector::arg_vector(const std::string& arg) { + arg_vector_t::arg_vector_t(const std::string& arg) { insertAndSort(arg); } - arg_vector::arg_vector(const char* arg) { + arg_vector_t::arg_vector_t(const char* arg) { insertAndSort(arg); } - arg_vector& arg_vector::operator=(const std::string& arg) { + arg_vector_t& arg_vector_t::operator=(const std::string& arg) { insertAndSort(arg); return *this; } - arg_vector& arg_vector::operator=(const char* arg) { + arg_vector_t& arg_vector_t::operator=(const char* arg) { insertAndSort(arg); return *this; } - arg_vector& arg_vector::operator=(std::initializer_list& args) { + arg_vector_t& arg_vector_t::operator=(std::initializer_list& args) { for (auto& arg : args) insertAndSort(arg); return *this; } - arg_vector& arg_vector::operator=(std::vector& args) { + arg_vector_t& arg_vector_t::operator=(std::vector& args) { for (auto& arg : args) insertAndSort(arg); return *this; } - void arg_vector::insertAndSort(const std::string& str) { + void arg_vector_t::insertAndSort(const std::string& str) { if (str.starts_with('-')) flags.push_back(str); else names.push_back(str); } - arg_nargs::arg_nargs(char c) { + arg_nargs_t::arg_nargs_t(char c) { decode(c); } - arg_nargs::arg_nargs(std::string s) { + arg_nargs_t::arg_nargs_t(std::string s) { decode(s[0]); } - arg_nargs::arg_nargs(const char* s) { + arg_nargs_t::arg_nargs_t(const char* s) { decode(*s); } - arg_nargs& arg_nargs::operator=(const std::string& s) { + arg_nargs_t& arg_nargs_t::operator=(const std::string& s) { decode(s[0]); return *this; } - arg_nargs& arg_nargs::operator=(char c) { + arg_nargs_t& arg_nargs_t::operator=(char c) { decode(c); return *this; } - arg_nargs& arg_nargs::operator=(int a) { + arg_nargs_t& arg_nargs_t::operator=(int a) { args = a; return *this; } - arg_nargs& arg_nargs::operator=(const char* s) { + arg_nargs_t& arg_nargs_t::operator=(const char* s) { decode(*s); return *this; } - void arg_nargs::decode(char c) { + void arg_nargs_t::decode(char c) { if (c == '?') - flags |= UNKNOWN; - if (c == '+') - flags |= ALL_REQUIRED; - if (c == '*') - flags |= ALL; + flags = UNKNOWN; + else if (c == '+') + flags = ALL_REQUIRED; + else if (c == '*') + flags = ALL; + else + flags = 0; } - arg_tokenizer::arg_tokenizer(size_t argc, const char** argv) { + arg_tokenizer_t::arg_tokenizer_t(size_t argc, const char** argv) { for (size_t i = 0; i < argc; i++) args.emplace_back(argv[i]); } - bool argparse::validateArgument(const arg_properties& args) { + bool argparse::validateArgument(const arg_properties_t& args) { return !args.a_flags.getFlags().empty() ^ !args.a_flags.getNames().empty(); } - void argparse::addArgument(const arg_properties& args) { + void argparse::addArgument(const arg_properties_t& args) { if (!validateArgument(args)) { BLT_WARN("Argument contains both flags and positional names, this is not allowed!"); BLT_WARN("(Discarding argument)"); return; } // store one copy of the properties (arg properties could in theory be very large!) - auto pos = user_args.argStorage.size(); - user_args.argStorage.push_back(args); - auto& arg = user_args.argStorage[pos]; + auto pos = user_args.arg_storage.size(); + user_args.arg_storage.push_back(args); + auto& arg = user_args.arg_storage[pos]; // associate the arg properties per name and flag to allow for quick access when parsing auto& names = args.a_flags.getNames(); for (const auto& name : names) - user_args.nameAssociations.emplace_back(name, &arg); + user_args.name_associations.emplace_back(name, &arg); auto& flags = args.a_flags.getFlags(); for (const auto& flag : flags) - user_args.flagAssociations[flag] = &arg; + user_args.flag_associations[flag] = &arg; + + if (args.a_required) + user_args.required_vars.insert(args.a_flags); } const argparse::arg_results& argparse::parse_args(int argc, const char** argv) { loaded_args = {}; - arg_tokenizer asToken(argc, argv); - loaded_args.programName = asToken.next(); - BLT_TRACE("Loading args for %s", loaded_args.programName.c_str()); + arg_tokenizer_t arg_tokenizer(argc, argv); + loaded_args.program_name = arg_tokenizer.next(); + BLT_TRACE("Loading args for %s", loaded_args.program_name.c_str()); - size_t lastPositional; - while (asToken.hasNext()) { - if (!asToken.isFlag()){ - loaded_args.positionalArgs[user_args.nameAssociations[lastPositional].first] = asToken.next(); + size_t last_positional; + while (arg_tokenizer.hasNext()) { + // a token isn't a flag it must be a positional arg as flags will consume nargs + if (!arg_tokenizer.isFlag()){ + handlePositionalArgument(arg_tokenizer, last_positional); continue; } - + handleFlagArgument(arg_tokenizer); } return loaded_args; } + void argparse::printHelp() { + + } + + void argparse::handlePositionalArgument(arg_tokenizer_t& arg_tokenizer, size_t& last_pos) { + // TODO: + loaded_args.positional_args[user_args.name_associations[last_pos++].first] = arg_tokenizer.next(); + } + + void argparse::handleFlagArgument(arg_tokenizer_t& arg_tokenizer) { + // token is a flag, find out special information about it + auto flag = arg_tokenizer.next(); + + auto loc = user_args.flag_associations.find(flag); + if (loc == user_args.flag_associations.end()){ + BLT_WARN("Flag '%s' not an option!"); + printHelp(); + return; + } + + auto flag_properties = loc->second; + + arg_data_t data; + switch(flag_properties->a_action){ + case arg_action_t::HELP: + printHelp(); + break; + case arg_action_t::STORE: + break; + case arg_action_t::STORE_CONST: + data = flag_properties->a_const; + break; + case arg_action_t::STORE_FALSE: + data = false; + break; + case arg_action_t::STORE_TRUE: + data = true; + break; + case arg_action_t::APPEND: + if (holds_alternative>(data)){ + auto& l = get>(data); + + } else + BLT_WARN("Requested append to data which doesn't contain a list!"); + break; + case arg_action_t::APPEND_CONST: + break; + case arg_action_t::COUNT: + break; + case arg_action_t::EXTEND: + break; + case arg_action_t::VERSION: + break; + } + loaded_args.flag_args[flag_properties->a_flags] = data; + } + + bool argparse::consumeFlagArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, arg_data_t& arg_data) { + // since the function is called after the flag is consumed, isFlag()/ will return information about the next arg + if (properties.a_nargs.flags) { + + } else { + if (arg_tokenizer.isFlag()) + return false; + + } + return false; + } + } \ No newline at end of file