• argument_parser.hpp
  • #pragma once
    #include "base_convention.hpp"
    #include <atomic>
    #include <cstdlib>
    #include <functional>
    #include <initializer_list>
    #include <iostream>
    #include <memory>
    #include <sstream>
    #include <stdexcept>
    #include <typeinfo>
    #include <unordered_map>
    #include <vector>
    #ifndef ARGUMENT_PARSER_HPP
    #define ARGUMENT_PARSER_HPP
    
    #include <type_traits>
    #include <string>
    
    namespace argument_parser {
        namespace function {
            template<typename fn>
            concept no_param = std::is_invocable_r_v<void, fn>;
    
            template<typename fn, typename param_type> 
            concept with_param = std::is_invocable_r_v<void, fn, param_type const&>; 
    
            template<typename fn>
            concept string_parameter = with_param<fn, std::string>;
            
            template<typename fn, typename param_type> 
            concept integral_parameter = with_param<fn, param_type> && std::is_integral_v<param_type>; 
    
            template<typename T_>
            using parametered_action = std::function<void(T_ const&)>;
    
            using non_parametered_action = std::function<void()>; 
        }
    
        namespace type {
            enum class parameter_type {
                NONE,
                INT,
                FLOAT,
                BOOL,
                STRING
            }; 
    
            template<typename T>
            struct param_type_map;
    
            template<> struct param_type_map<int>    { static constexpr parameter_type value = parameter_type::INT; };
            template<> struct param_type_map<float>  { static constexpr parameter_type value = parameter_type::FLOAT; };
            template<> struct param_type_map<bool>   { static constexpr parameter_type value = parameter_type::BOOL; };
            template<> struct param_type_map<std::string> { static constexpr parameter_type value = parameter_type::STRING; };
    
            template<typename T>
            concept supported_parameter_type = requires {
                { param_type_map<T>::value } -> std::convertible_to<parameter_type>;
            };
        }
    
        namespace error {
            enum class type {
                missing_parameter,
                key_redefined
            };
    
            constexpr type missing_parameter = type::missing_parameter; 
            constexpr type key_redefined = type::key_redefined; 
        }
    
        template <typename T_> class parametered_action;
        class non_parametered_action;
    
        class base_action {}; 
    
        template<typename child>
        class action_impl : public base_action {
            public: 
            void invoke() {
                static_cast<child*>(this)->invoke_impl(); 
            } 
    
            template<typename T_> 
            void invoke(T_ const& var) {
                if (!validate_type(parametered_action<T_>{})) 
                    throw std::runtime_error("this action does not take any parameter, or given parameters do not match the required parameter type."); 
                static_cast<child*>(this)->invoke_impl(var);
            }  
            
            std::type_info const& get_type() const {
                return static_cast<child const*>(this)->get_type_impl(); 
            }
    
            template<typename T>
            bool validate_type(action_impl<T> const& other) {
                return get_type() == other.get_type();  
            }
        }; 
        
        template <typename T_> 
        class parametered_action : public action_impl<parametered_action<T_>> {
            public: 
            parametered_action() {}
            parametered_action(function::parametered_action<T_> const& function) : handler(function) {}
            template<typename T>
            static std::type_info const& type() {
                return typeid(parametered_action<T>); 
            }
    
            std::type_info const& get_type_impl() const {
                return type<T_>();        
            }
            private:
            function::parametered_action<T_> handler; 
            template<typename arg_type>
            void invoke_impl(arg_type const& arg) {
                handler(arg); 
            }
    
            friend class action_impl<parametered_action<T_>>; 
        }; 
        
        class non_parametered_action : public action_impl<non_parametered_action> {
            public: 
            non_parametered_action(std::function<void()> const& function) : handler(function) {}
            static std::type_info const& type() {
                return typeid(non_parametered_action); 
            }
    
            std::type_info const& get_type_impl() const {
                return type();        
            }
            private: 
            std::function<void()> handler; 
            void invoke_impl() {
                this->handler(); 
            }
    
            friend class action_impl<non_parametered_action>; 
        };
    
        class helpers {
            public: 
            template<typename T_>
            static std::shared_ptr<parametered_action<T_>> make_parametered_action_ptr(function::parametered_action<T_> const& function) {
                return std::make_shared<parametered_action<T_>>(parametered_action<T_>(function));
            }
            
            template<typename T_> 
            static parametered_action<T_> make_parametered_action(function::parametered_action<T_> const& function) {
                return parametered_action<T_>(function); 
            }
    
            static non_parametered_action make_non_parametered_action(function::non_parametered_action const& function) {
                return non_parametered_action(function); 
            }
    
            static std::shared_ptr<non_parametered_action> make_non_parametered_action_ptr(function::non_parametered_action const& function) {
                return std::make_shared<non_parametered_action>(non_parametered_action(function)); 
            }
        };
    
        class argument {
            using parameter_type = type::parameter_type; 
            public:
            argument() : name(), id(0) {}
            argument(std::string const& name, int id) : name(name), id(id) {}
    
            // parametered action group
            template<type::supported_parameter_type T_>
            argument(int id, std::string const& name, parametered_action<T_> const& action) : argument(name, id) {
                type = extract_type<T_>(); 
                this->action = std::make_shared<parametered_action<T_>>(action);
            }
    
            template<type::supported_parameter_type T_>
            argument(int id, std::string const& name, std::shared_ptr<parametered_action<T_>> const& action) : argument(name, id) {
                type = extract_type<T_>(); 
                this->action = action; 
            }
    
            template<type::supported_parameter_type T_>
            argument(std::string const& name, parametered_action<T_> const& action) : argument(name, 0) {
                type = extract_type<T_>(); 
                this->action = std::make_shared<parametered_action<T_>>(action);
            }
    
            template<type::supported_parameter_type T_>
            argument(std::string const& name, std::shared_ptr<parametered_action<T_>> const& action) : argument(name, 0) {
                type = extract_type<T_>(); 
                this->action = action; 
            }
    
            // non parametered action group 
            argument(int id, std::string const& name, non_parametered_action const& action) : argument(name, id) {
                type = parameter_type::NONE;
                this->action = std::make_shared<non_parametered_action>(action);
            }
    
            argument(int id, std::string const& name, std::shared_ptr<non_parametered_action> const& action) : argument(name, id) {
                type = parameter_type::NONE; 
                this->action = action; 
            }
    
            argument(std::string const& name, non_parametered_action const& action) : argument(name, 0) {
                type = parameter_type::NONE;
                this->action = std::make_shared<non_parametered_action>(action);
            }
    
            argument(std::string const& name, std::shared_ptr<non_parametered_action> const& action) : argument(name, 0) {
                type = parameter_type::NONE;
                this->action = action; 
            }
    
            argument(argument const& other) : 
                id(other.id), 
                name(other.name), 
                action(other.action), 
                required(other.required), 
                type(other.type),
                help_text(other.help_text),
                invoked(other.invoked) 
            {}
            argument& operator=(argument const& other) {
                this->~argument(); 
                new(this) argument{other};
                return *this; 
            }
            
            void toggle_required() {
                required = !required; 
            }
    
    
            bool is_required() const {
                return required; 
            } 
    
            std::string get_name() const {
                return name; 
            }
    
            parameter_type expected_type() const {
                return type; 
            }
    
            bool expects_parameter() const {
                return type != parameter_type::NONE; 
            }
    
            bool is_invoked() const {
                return invoked; 
            }
    
            template<typename T_> 
            void invoke(T_ const& argument) {
                if (type == parameter_type::NONE) throw std::runtime_error("this argument does not expect any parameter.");
                invoked = true; 
                static_cast<action_impl<parametered_action<T_>>*>(action.get())->invoke(argument); 
            }
    
            void invoke() {
                if (type != parameter_type::NONE) throw std::runtime_error("this argument expects parameter."); 
                invoked = true; 
                static_cast<action_impl<non_parametered_action>*>(action.get())->invoke();
            }
    
            private: 
            void set_required(bool val) {
                required = val; 
            }
    
            void set_help_text(std::string const& help_text) {
                this->help_text = help_text; 
            }
            argument(type::parameter_type const& parameter_type) : id(0), name(), type(parameter_type) {}
            friend class base_parser; 
            template<type::supported_parameter_type T_>
            constexpr type::parameter_type extract_type() {
                if constexpr (std::is_same_v<T_, int>)
                    return type::parameter_type::INT;
                else if constexpr (std::is_same_v<T_, float> || std::is_convertible_v<T_, float>)
                    return type::parameter_type::FLOAT;
                else if constexpr (std::is_same_v<T_, bool>)
                    return type::parameter_type::BOOL;
                else if constexpr (std::is_same_v<T_, std::string> || std::is_convertible_v<T_, std::string>)
                    return type::parameter_type::STRING;
            }
    
    
            int const id;
            std::string const name;
            std::shared_ptr<base_action> action;
            bool required = false; 
            parameter_type type;
            bool invoked = false; 
            std::string help_text; 
        }; 
    
        class base_parser { 
            public: 
            template<typename T_>
            void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action<T_> const& action) {
                base_add_argument(short_arg, long_arg, long_arg, action, false); 
            }
    
            template<typename T_>
            void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action<T_> const& action, bool required) {
                base_add_argument(short_arg, long_arg, long_arg, action, required); 
            }
    
            template<typename T_>
            void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action<T_> const& action, bool required) {
                base_add_argument(short_arg, long_arg, help_text, action, required); 
            }
    
            void add_argument(std::string const& short_arg, std::string const& long_arg, non_parametered_action const& action) {
                base_add_argument(short_arg, long_arg, long_arg, action, false); 
            }
    
            void add_argument(std::string const& short_arg, std::string const& long_arg, non_parametered_action const& action, bool required) {
                base_add_argument(short_arg, long_arg, long_arg, action, required); 
            }
    
            void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, non_parametered_action const& action, bool required) {
                base_add_argument(short_arg, long_arg, help_text, action, required); 
            }
    
            void handle_arguments(std::initializer_list<conventions::convention const* const> convention_types) {
                for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) {
                    std::stringstream ss;  
                    bool arg_correctly_handled = false; 
                    for(auto const& convention_type : convention_types) {
                        auto extracted = convention_type->get_argument(*it); 
                        if (extracted.first == conventions::argument_type::ERROR) {
                            ss << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n";
                            continue;
                        }
                        
                        try {
                            argument& coresponding_argument = get_argument(extracted);
                            if (coresponding_argument.expects_parameter()) {
                                auto type = coresponding_argument.expected_type();
                                if (convention_type->requires_next_token() and (it + 1) == parsed_arguments.end()) 
                                    throw std::runtime_error("expected value for argument " + extracted.second + " argument"); 
                                auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it); 
                                if (type == type::parameter_type::BOOL) {
                                    auto value = parse_bool(value_raw);
                                    coresponding_argument.invoke<bool>(value); 
                                    arg_correctly_handled = true;
                                    break; 
                                }
                                else if (type == type::parameter_type::INT) {
                                    auto value = parse_int(value_raw); 
                                    coresponding_argument.invoke<int>(value); 
                                    arg_correctly_handled = true;
                                    break; 
                                } 
                                else if (type == type::parameter_type::FLOAT) {
                                    auto value = parse_float(value_raw); 
                                    coresponding_argument.invoke<float>(value); 
                                    arg_correctly_handled = true;
                                    break; 
                                } 
                                else if (type == type::parameter_type::STRING) {
                                    coresponding_argument.invoke<std::string>(value_raw); 
                                    arg_correctly_handled = true; 
                                    break; 
                                }
                            }
                            else {
                                coresponding_argument.invoke();
                                arg_correctly_handled = true;  
                                break;
                            }    
                        } catch(std::runtime_error e) {
                            ss << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; 
                            continue;
                        }
                    }
                    if (!arg_correctly_handled) {
                        throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + ss.str());
                    }
                }
    
                std::vector<std::pair<std::string, std::string>> required_args; 
                for (auto const& [_, arg] : argument_map) {
                    if (arg.is_required() and not arg.is_invoked()) {
                        required_args.emplace_back<std::pair<std::string, std::string>>({
                            reverse_short_arguments[arg.id],
                            reverse_long_arguments[arg.id]
                        });
                    } 
                }
    
                if (not required_args.empty()) {
                    std::cerr << "These arguments were expected but not provided: "; 
                    for (auto const& [s, l] : required_args) {
                        std::cerr << "[-" << s << ", --" << l << "] ";
                    }
                    std::cerr << "\n"; 
                    display_help(convention_types); 
                }
            }
    
            void display_help(std::initializer_list<conventions::convention const* const> convention_types) {
                std::cout << build_help_text(convention_types); 
            }
    
            std::string build_help_text(std::initializer_list<conventions::convention const* const> convention_types) {
                std::stringstream ss;
                
                ss << "Usage: " << program_name
                        << " [OPTIONS]... [ARGS]...\n"; 
                    
                
                for (auto  const& [id, arg] : argument_map) {
                    auto short_arg = reverse_short_arguments[id];
                    auto long_arg = reverse_long_arguments[id]; 
                    ss << "\t";
                    for (auto const& convention : convention_types) {
                        ss << convention->short_prec() << short_arg << ", ";  
                    }
                    bool first = true; 
                    for (auto const& convention : convention_types) {
                        ss << (first ? "" : ", ") << convention->long_prec() << long_arg;  
                        first = false; 
                    }
    
                    ss << "\t\t" << arg.help_text << "\n"; 
                }
    
                return ss.str(); 
            }
    
            private:
            bool parse_bool(std::string const& val) {
                if (val == "t" || val == "true" || val == "1") return true; 
                if (val == "f" || val == "false" || val == "0") return false; 
                throw std::runtime_error("expected boolean parsable, got:" + val); 
            }
    
            int parse_int(std::string const& val) {
                return std::stoi(val); 
            }
    
            int parse_float(std::string const& val) {
                return std::stof(val); 
            }
    
            base_parser() : argument_map(), short_arguments(), long_arguments(), reverse_long_arguments(), reverse_short_arguments(), parsed_arguments(), program_name() {}
    
            template<typename T_> 
            void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, T_ const& action, bool required) {
                if (short_arguments.find(short_arg) != short_arguments.end() || long_arguments.find(long_arg) != long_arguments.end()) {
                    throw std::runtime_error("The key already exists!"); 
                }
    
                int id = __id_ref.fetch_add(1);
                
                auto arg = argument(id, short_arg + "|" + long_arg, action);;
                arg.set_required(required); 
                arg.set_help_text(help_text);
    
                argument_map[id] = arg;  
                short_arguments[short_arg] = id;
                reverse_short_arguments[id] = short_arg; 
                long_arguments[long_arg] = id;  
                reverse_long_arguments[id] = long_arg; 
            }
    
            public: 
            argument& get_argument(conventions::parsed_argument const& arg) {
                if (arg.first == conventions::argument_type::LONG) {
                    auto long_pos = long_arguments.find(arg.second);
                    if (long_pos != long_arguments.end()) return argument_map[long_pos->second]; 
                } else if (arg.first == conventions::argument_type::SHORT) {
                    auto short_pos = short_arguments.find(arg.second); 
                    if (short_pos != short_arguments.end()) return argument_map[short_pos->second];
                } else if (arg.first == conventions::argument_type::ERROR) {
                    throw std::runtime_error{arg.second}; 
                }
                throw std::runtime_error("Unknown argument: " + arg.second);
            }
    
            private:
            std::string program_name;
    
            inline static std::atomic_int __id_ref = std::atomic_int(0);
    
            std::unordered_map<int, argument> argument_map; 
            std::unordered_map<std::string, int> short_arguments; 
            std::unordered_map<int, std::string> reverse_short_arguments; 
            std::unordered_map<std::string, int> long_arguments; 
            std::unordered_map<int, std::string> reverse_long_arguments; 
    
            std::vector<std::string> parsed_arguments; 
    
            friend class linux_parser; 
            friend class windows_parser; 
        };
    }
    
    #endif // ARGUMENT_PARSER_HPP