#pragma once #include "base_convention.hpp" #include #include #include #include #include #include #include #include #include #include #include #ifndef ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP #include #include namespace argument_parser { namespace function { template concept no_param = std::is_invocable_r_v; template concept with_param = std::is_invocable_r_v; template concept string_parameter = with_param; template concept integral_parameter = with_param && std::is_integral_v; template using parametered_action = std::function; using non_parametered_action = std::function; } namespace type { enum class parameter_type { NONE, INT, FLOAT, BOOL, STRING }; template struct param_type_map; template<> struct param_type_map { static constexpr parameter_type value = parameter_type::INT; }; template<> struct param_type_map { static constexpr parameter_type value = parameter_type::FLOAT; }; template<> struct param_type_map { static constexpr parameter_type value = parameter_type::BOOL; }; template<> struct param_type_map { static constexpr parameter_type value = parameter_type::STRING; }; template concept supported_parameter_type = requires { { param_type_map::value } -> std::convertible_to; }; } namespace error { enum class type { missing_parameter, key_redefined }; constexpr type missing_parameter = type::missing_parameter; constexpr type key_redefined = type::key_redefined; } template class parametered_action; class non_parametered_action; class base_action {}; template class action_impl : public base_action { public: void invoke() { static_cast(this)->invoke_impl(); } template void invoke(T_ const& var) { if (!validate_type(parametered_action{})) throw std::runtime_error("this action does not take any parameter, or given parameters do not match the required parameter type."); static_cast(this)->invoke_impl(var); } std::type_info const& get_type() const { return static_cast(this)->get_type_impl(); } template bool validate_type(action_impl const& other) { return get_type() == other.get_type(); } }; template class parametered_action : public action_impl> { public: parametered_action() {} parametered_action(function::parametered_action const& function) : handler(function) {} template static std::type_info const& type() { return typeid(parametered_action); } std::type_info const& get_type_impl() const { return type(); } private: function::parametered_action handler; template void invoke_impl(arg_type const& arg) { handler(arg); } friend class action_impl>; }; class non_parametered_action : public action_impl { public: non_parametered_action(std::function 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 handler; void invoke_impl() { this->handler(); } friend class action_impl; }; class helpers { public: template static std::shared_ptr> make_parametered_action_ptr(function::parametered_action const& function) { return std::make_shared>(parametered_action(function)); } template static parametered_action make_parametered_action(function::parametered_action const& function) { return parametered_action(function); } static non_parametered_action make_non_parametered_action(function::non_parametered_action const& function) { return non_parametered_action(function); } static std::shared_ptr make_non_parametered_action_ptr(function::non_parametered_action const& function) { return std::make_shared(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 argument(int id, std::string const& name, parametered_action const& action) : argument(name, id) { type = extract_type(); this->action = std::make_shared>(action); } template argument(int id, std::string const& name, std::shared_ptr> const& action) : argument(name, id) { type = extract_type(); this->action = action; } template argument(std::string const& name, parametered_action const& action) : argument(name, 0) { type = extract_type(); this->action = std::make_shared>(action); } template argument(std::string const& name, std::shared_ptr> const& action) : argument(name, 0) { type = extract_type(); 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(action); } argument(int id, std::string const& name, std::shared_ptr 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(action); } argument(std::string const& name, std::shared_ptr 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) { if (this == &other) return *this; 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 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.get())->invoke(argument); } void invoke() { if (type != parameter_type::NONE) throw std::runtime_error("this argument expects parameter."); invoked = true; static_cast*>(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 constexpr type::parameter_type extract_type() { if constexpr (std::is_same_v) return type::parameter_type::INT; else if constexpr (std::is_same_v || std::is_convertible_v) return type::parameter_type::FLOAT; else if constexpr (std::is_same_v) return type::parameter_type::BOOL; else if constexpr (std::is_same_v || std::is_convertible_v) return type::parameter_type::STRING; } int const id; std::string const name; std::shared_ptr action; bool required = false; parameter_type type; bool invoked = false; std::string help_text; }; class base_parser { public: template void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action const& action) { base_add_argument(short_arg, long_arg, long_arg, action, false); } template void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action const& action, bool required) { base_add_argument(short_arg, long_arg, long_arg, action, required); } template void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action 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 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(value); arg_correctly_handled = true; break; } else if (type == type::parameter_type::INT) { auto value = parse_int(value_raw); coresponding_argument.invoke(value); arg_correctly_handled = true; break; } else if (type == type::parameter_type::FLOAT) { auto value = parse_float(value_raw); coresponding_argument.invoke(value); arg_correctly_handled = true; break; } else if (type == type::parameter_type::STRING) { coresponding_argument.invoke(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> required_args; for (auto const& [_, arg] : argument_map) { if (arg.is_required() and not arg.is_invoked()) { required_args.emplace_back>({ 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 convention_types) { std::cout << build_help_text(convention_types); } std::string build_help_text(std::initializer_list 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 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 argument_map; std::unordered_map short_arguments; std::unordered_map reverse_short_arguments; std::unordered_map long_arguments; std::unordered_map reverse_long_arguments; std::vector parsed_arguments; friend class linux_parser; friend class windows_parser; }; } #endif // ARGUMENT_PARSER_HPP