• ArgParser.hpp
  • #pragma once
    
    #include <exception>
    #include <string>
    #include <functional>
    #include <sstream>
    
    namespace ArgParser {
        namespace ArgumentFunction {
            template<typename Fn>
            concept NoParam = std::is_invocable_r_v<void, Fn>; 
            
            template<typename Fn, typename ParamType>
            concept WithParam = std::is_invocable_r_v<void, Fn, ParamType const&>; 
    
            template<typename Fn>
            concept WithString = WithParam<Fn, std::string>; 
    
            template<typename Fn>
            concept WithInteger = WithParam<Fn, std::string>; 
            
            template<typename Fn>
            concept WithFloat = WithParam<Fn, std::string>; 
        };
    
        
    
        enum class ErrorType {
            MissingValue,
            KeyExists,
        }; 
    
        class ParseArgument {
            public: 
            ParseArgument(std::string const& arg_prec = "-");
    
            void SetHelp(std::string const& help);
    
            void SetErrorText(ErrorType type, std::string const& error_message);
    
            template<ArgumentFunction::NoParam Fn>
            void AddArgument(std::string const& argument_short, std::string const& argument_long, Fn const& function) {
                FailIfExists({argument_short, argument_long});
                user_defined_arg_list.emplace_back(argument_short, argument_long, new NoParamHandler(function), false);
    
                prepare_usage_text(argument_short, argument_long); 
    
                if(not default_help_text) return; 
    
                prepare_help_text_from_long_key(argument_short, argument_long); 
            }
    
            template<typename ParamType = std::string, ArgumentFunction::WithParam<ParamType> Fn>
            void AddArgument(std::string const& argument_short, std::string const& argument_long, Fn const& function) {
                FailIfExists({argument_short, argument_long});
                user_defined_arg_list.emplace_back(argument_short, argument_long, new ParamHandler<ParamType>(function), true);
    
                prepare_usage_text_with_value(argument_short, argument_long); 
    
                if(not default_help_text) return; 
    
                prepare_help_text_from_long_key(argument_short, argument_long, true); 
            }
    
            template<ArgumentFunction::NoParam Fn>
            void AddArgument(std::string const& argument_short, std::string const& argument_long, std::string const& help, Fn const& function) {
                FailIfExists({argument_short, argument_long});
                user_defined_arg_list.emplace_back(argument_short, argument_long, new NoParamHandler(function), false);
    
                prepare_usage_text(argument_short, argument_long); 
    
                if(not default_help_text) return; 
    
                help_text << arg_prec << argument_short << ", " << arg_prec << arg_prec << argument_long << " : " << help << "\n";
            }
    
            template<typename ParamType = std::string, ArgumentFunction::WithParam<ParamType> Fn>
            void AddArgument(std::string const& argument_short, std::string const& argument_long, std::string const& help, Fn const& function) {
                FailIfExists({argument_short, argument_long});
                user_defined_arg_list.emplace_back(argument_short, argument_long, new ParamHandler<ParamType>(function), true);
    
                prepare_usage_text_with_value(argument_short, argument_long); 
    
                if(not default_help_text) return; 
    
                help_text << arg_prec << argument_short << " <value>" << ", " << arg_prec << arg_prec << argument_long << " <value>" << " : " << help << "\n";
            }
    
            void HandleArguments();
    
            private:
            std::pair<std::string, std::string> split(std::string const& str, char delim = '=');
            std::pair<bool, bool> IsKeyExists(std::pair<std::string, std::string> const& keys);
            void FailIfExists(std::pair<std::string, std::string> const& keys) {
                auto [se, le] = IsKeyExists(keys);
                if(se) {
                    throw KeyExists(keys.first + Errors.key_exists); 
                }
                if(le) {
                    throw KeyExists(keys.second + Errors.key_exists); 
                }
            }
    
            class BaseHandler {
                protected:
                std::string __type;
                public: 
                std::string GetType() {
                    return __type; 
                }
            };
            class NoParamHandler : public BaseHandler {
                private:
                std::function<void()> handler;
                public: 
                NoParamHandler(std::function<void()> function) : handler(function) {}
                void operator()() {
                    handler(); 
                }
                void Invoke() {
                    handler(); 
                }
            }; 
    
            template<typename T_>
            class ParamHandler : public BaseHandler {
                private:
                std::function<void(T_ const&)> handler;
                public: 
                ParamHandler(std::function<void(T_ const&)> function) : handler(function) {
                    if constexpr (std::is_same_v<T_, int>) {
                        BaseHandler::__type = "int"; 
                    }
                    else if constexpr (std::is_same_v<T_, float>) {
                        BaseHandler::__type = "float"; 
                    }
                    else if constexpr (std::is_same_v<T_, std::string>) {
                        BaseHandler::__type = "string"; 
                    } 
                    else if constexpr (std::is_same_v<T_, bool>) {
                        BaseHandler::__type = "bool";
                    }
                    else {
                        static_assert(false, "Unsupported type.");
                    }
                }
                void operator()(T_ const& val) {
                    handler(val); 
                }
                void Invoke(T_ const& val) {
                    handler(val); 
                }
                
            }; 
        
            struct Name {
                public:
                std::string _short; 
                std::string _long;
                bool does_match(std::string const& val, std::string const& arg_prec) {
                    if (val == (arg_prec) + _short) {
                        matched = _short; 
                        return true; 
                    }
                    if (val == (arg_prec + arg_prec) + _long) {
                        matched = _long; 
                        return true; 
                    }
                    matched.clear(); 
                    return false; 
                }
                std::string get_matched() const {
                    return matched; 
                }
                private:
                std::string matched; 
            };
            struct Data {
                std::string value; 
                bool has_value = false; 
            };
            struct Keys {
                Name name; 
                Data data;   
            };
            struct ArgumentType {
                Keys keys; 
                BaseHandler* handler;
                ArgumentType() = default; 
                ArgumentType(std::string const& _short, std::string const& _long, BaseHandler * _handler, bool has_value = false) {
                    keys.name._short = _short; 
                    keys.name._long = _long;
                    handler = _handler; 
                    keys.data.has_value = has_value; 
                }
            };
            class BaseError : public std::exception {
                private:
                std::string msg; 
                public: 
                BaseError(const std::string& message = "Base error.") : msg(message) {}
                const char * what()  const noexcept override {
                    return msg.c_str(); 
                }
            };
    
            class MissingValue : public BaseError {
                public:
                MissingValue(std::string const& msg) : BaseError(msg) {}
            }; 
            class KeyExists : public BaseError {
                public:
                KeyExists(std::string const& msg) : BaseError(msg) {}
            }; 
            struct {
                std::string missing_value = "A value must be supplied."; 
                std::string key_exists = " already exists.";
            } Errors; 
    
            void help();
            void prepare_help_text_from_long_key(std::string const& argument_short, std::string const& argument_long, bool has_value = false);
            void prepare_usage_text(std::string const& argument_short, std::string const& argument_long) {
                usage << "[" << arg_prec << argument_short << " | " << (arg_prec + arg_prec) << argument_long << "]";
            }
            void prepare_usage_text_with_value(std::string const& argument_short, std::string const& argument_long) {
                usage << "[" << arg_prec << argument_short << " <value> " << " | " << (arg_prec + arg_prec) << argument_long << " <value>" << "]";
            }
    
            std::vector<ArgumentType> user_defined_arg_list; 
            std::vector<std::string> arg_list;
            std::stringstream help_text;
            std::stringstream usage; 
            bool default_help_text = true;
            std::string arg_prec;
            std::string program_name; 
        }; 
    
    };