/* * Created by Brett on 06/08/23. * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ #ifndef BLT_TESTS_ARGPARSE_2_H #define BLT_TESTS_ARGPARSE_2_H #include #include #include #include #include #include #include namespace blt { typedef std::variant arg_data_internal_t; typedef std::vector arg_data_vec_t; typedef std::variant arg_data_t; enum class arg_action_t { STORE, STORE_CONST, STORE_TRUE, STORE_FALSE, APPEND, APPEND_CONST, COUNT, HELP, VERSION, EXTEND }; class arg_vector_t { friend class arg_parse; private: std::vector flags; std::string name; void validateFlags(); public: arg_vector_t(std::vector flags): flags(std::move(flags)) { validateFlags(); } arg_vector_t(std::initializer_list flags): flags(flags) { validateFlags(); } arg_vector_t(const char* str); arg_vector_t(const std::string& str); [[nodiscard]] inline bool isFlag() const { return !flags.empty(); } [[nodiscard]] inline bool contains(const std::string& str) { return std::any_of( flags.begin(), flags.end(), [&str](const std::string& flag) -> bool { return flag == str; } ) || str == name; } }; class arg_nargs_t { friend class arg_parse; private: static constexpr int UNKNOWN = 0x1; static constexpr int ALL = 0x2; static constexpr int ALL_REQUIRED = 0x4; // 0 means ignore int args = 1; // 0 indicates args is used int flags = 0; void decode(char c); public: arg_nargs_t() = default; arg_nargs_t(int args): args(args) {} arg_nargs_t(char c); arg_nargs_t(std::string s); arg_nargs_t(const char* s); }; struct arg_properties_t { private: public: arg_vector_t a_flags; arg_action_t a_action = arg_action_t::STORE; arg_nargs_t a_nargs = 1; std::string a_const{}; arg_data_internal_t a_default{}; std::string a_dest{}; std::string a_help{}; std::string a_version{}; std::string a_metavar{}; bool a_required = true; }; class arg_builder { arg_properties_t properties; public: arg_builder(const arg_vector_t& flags): properties(flags) {} inline arg_properties_t build() { return properties; } inline arg_builder& setAction(arg_action_t action) { properties.a_action = action; return *this; } inline arg_builder& setNArgs(const arg_nargs_t& nargs) { properties.a_nargs = nargs; return *this; } inline arg_builder& setConst(const std::string& a_const) { properties.a_const = a_const; return *this; } inline arg_builder& setDefault(const arg_data_internal_t& def) { properties.a_default = def; return *this; } inline arg_builder& setDest(const std::string& dest) { properties.a_dest = dest; return *this; } inline arg_builder& setHelp(const std::string& help) { properties.a_help = help; return *this; } inline arg_builder& setVersion(const std::string& version) { properties.a_version = version; return *this; } inline arg_builder& setMetavar(const std::string& metavar) { properties.a_metavar = metavar; return *this; } inline arg_builder& setRequired() { properties.a_required = true; return *this; } }; class arg_tokenizer { private: std::vector args; size_t currentIndex = 0; public: arg_tokenizer(std::vector args): args(std::move(args)) {} // returns the current arg inline const std::string& get() { return args[currentIndex]; } // returns if we have next arg to process inline bool hasNext() { return currentIndex + 1 < args.size(); } inline bool hasCurrent() { return currentIndex < args.size(); } // returns true if the current arg is a flag inline bool isFlag() { return args[currentIndex].starts_with('-'); } // returns true if we have next and the next arg is a flag inline bool isNextFlag() { return hasNext() && args[currentIndex + 1].starts_with('-'); } // advances to the next flag inline size_t advance() { return currentIndex++; } }; class arg_parse { private: struct { friend arg_parse; private: std::vector arg_properties_storage; public: std::vector name_associations; HASHMAP flag_associations; } user_args; struct arg_results { friend arg_parse; private: HASHSET found_args; std::vector unrecognized_args; public: std::string program_name; HASHMAP data; inline arg_data_t& operator[](const std::string& key) { return data[key]; } inline auto begin() { return data.begin(); } inline auto end() { return data.end(); } inline bool contains(const std::string& key) { return data.find(key) != data.end(); } } loaded_args; private: static std::string filename(const std::string& path); // expects that the current flag has already been consumed (advanced past), leaves tokenizer in a state where the next element is 'current' static bool consumeArguments(arg_tokenizer& tokenizer, const arg_properties_t& properties, std::vector& v_out); void handlePositionalArgument(arg_tokenizer& tokenizer, size_t& last_pos); void handleFlagArgument(arg_tokenizer& tokenizer); void processFlag(arg_tokenizer& tokenizer, const std::string& flag); template static inline bool holds_alternative(const arg_data_t& v){ if constexpr (std::is_same_v) return std::holds_alternative(v); else { if (std::holds_alternative(v)) { arg_data_internal_t data = std::get(v); return std::holds_alternative(data); } else return false; } } template static inline T& get(arg_data_t& v){ if constexpr (std::is_same_v) return std::get(v); else return std::get(std::get(v)); } public: arg_parse() { addArgument(arg_builder({"--help", "-h"}).setAction(arg_action_t::HELP).setHelp("Show this help menu").build()); }; void addArgument(const arg_properties_t& args); arg_results parse_args(int argc, const char** argv); arg_results parse_args(const std::vector& args); void printHelp(); ~arg_parse() { for (auto* p : user_args.arg_properties_storage) delete p; } }; std::string to_string(const blt::arg_data_t& v); std::string to_string(const blt::arg_data_internal_t& v); } #endif //BLT_TESTS_ARGPARSE_2_H