#pragma once #include "traits.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace argument_parser::v2 { enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required, Reference }; namespace flags { constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument; constexpr static inline add_argument_flags LongArgument = add_argument_flags::LongArgument; constexpr static inline add_argument_flags HelpText = add_argument_flags::HelpText; constexpr static inline add_argument_flags Action = add_argument_flags::Action; constexpr static inline add_argument_flags Required = add_argument_flags::Required; constexpr static inline add_argument_flags Positional = add_argument_flags::Positional; constexpr static inline add_argument_flags Position = add_argument_flags::Position; constexpr static inline add_argument_flags Reference = add_argument_flags::Reference; } // namespace flags class base_parser : private argument_parser::base_parser { public: template using typed_flag_value = std::variant, bool, int, T*>; using non_typed_flag_value = std::variant; template using typed_argument_pair = std::pair>; using non_typed_argument_pair = std::pair; template void add_argument(std::unordered_map> const &argument_pairs) { add_argument_impl, T>(argument_pairs); } template void add_argument(std::initializer_list> const &pairs) { std::unordered_map> args; for (auto &[k, v] : pairs) { args[k] = v; } add_argument(args); } void add_argument(std::initializer_list const &pairs) { std::unordered_map args; for (auto &[k, v] : pairs) { args[k] = v; } add_argument(args); } void add_argument(std::unordered_map const &argument_pairs) { add_argument_impl(argument_pairs); } argument_parser::base_parser &to_v1() { return *this; } void handle_arguments(std::initializer_list convention_types) { base::handle_arguments(convention_types); } template std::optional get_optional(std::string const &arg) { return base::get_optional(arg); } using argument_parser::base_parser::display_help; using argument_parser::base_parser::on_complete; protected: void set_program_name(std::string p) { base::program_name = std::move(p); } std::vector &ref_parsed_args() { return base::parsed_arguments; } using argument_parser::base_parser::current_conventions; using argument_parser::base_parser::reset_current_conventions; void prepare_help_flag(bool should_exit = true) { add_argument({{flags::ShortArgument, "h"}, {flags::LongArgument, "help"}, {flags::Action, helpers::make_non_parametered_action([this, should_exit]() { this->display_help(this->current_conventions()); if (should_exit) { std::exit(0); } })}, {flags::HelpText, "Prints this help text."}}); } private: template void add_argument_impl(ArgsMap const &argument_pairs) { if (argument_pairs.find(add_argument_flags::Positional) != argument_pairs.end()) { add_positional_argument_impl(argument_pairs); return; } std::unordered_map found_params{ {extended_add_argument_flags::IsTyped, IsTyped}}; std::string short_arg, long_arg, help_text; std::unique_ptr action; bool required = false; if (argument_pairs.find(add_argument_flags::ShortArgument) != argument_pairs.end()) { found_params[extended_add_argument_flags::ShortArgument] = true; short_arg = get_or_throw(argument_pairs.at(add_argument_flags::ShortArgument), "short"); } if (argument_pairs.find(add_argument_flags::LongArgument) != argument_pairs.end()) { found_params[extended_add_argument_flags::LongArgument] = true; long_arg = get_or_throw(argument_pairs.at(add_argument_flags::LongArgument), "long"); if (short_arg.empty()) short_arg = "-"; } else { if (!short_arg.empty()) long_arg = "-"; } if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) { found_params[extended_add_argument_flags::Action] = true; action = get_or_throw(argument_pairs.at(add_argument_flags::Action), "action").clone(); } if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) { help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); } if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { required = true; } if (argument_pairs.find(add_argument_flags::Reference) != argument_pairs.end()) { if (!IsTyped) { throw std::logic_error("Reference argument must be typed"); } found_params[extended_add_argument_flags::Action] = true; if constexpr (!std::is_same_v) { auto ref = get_or_throw(argument_pairs.at(add_argument_flags::Reference), "reference"); action = helpers::make_parametered_action([ref](T const& t) { *ref = t; }).clone(); } else { throw std::logic_error("Reference argument must not be void"); } } auto suggested_add = suggest_candidate(found_params); if (suggested_add == candidate_type::unknown) { throw std::runtime_error("Could not match any add argument overload to given parameters. Are you " "missing some required parameter?"); } if constexpr (IsTyped) { switch (suggested_add) { case candidate_type::typed_action: if (help_text.empty()) { if constexpr (internal::sfinae::has_format_hint>::value && internal::sfinae::has_purpose_hint>::value) { auto format_hint = parsing_traits::parser_trait::format_hint; auto purpose_hint = parsing_traits::parser_trait::purpose_hint; help_text = "Triggers action with " + std::string(purpose_hint) + " (" + std::string(format_hint) + ")"; } else { help_text = "Triggers action with value."; } } base::add_argument(short_arg, long_arg, help_text, *static_cast(&(*action)), required); break; case candidate_type::store_other: if (help_text.empty()) { if constexpr (internal::sfinae::has_format_hint>::value && internal::sfinae::has_purpose_hint>::value) { auto format_hint = parsing_traits::parser_trait::format_hint; auto purpose_hint = parsing_traits::parser_trait::purpose_hint; help_text = "Accepts " + std::string(purpose_hint) + " in " + std::string(format_hint) + " format."; } else { help_text = "Accepts value."; } } base::add_argument(short_arg, long_arg, help_text, required); break; default: throw std::runtime_error("Could not match the arguments against any overload."); } } else { switch (suggested_add) { case candidate_type::non_typed_action: if (help_text.empty()) { help_text = "Triggers action with no value."; } base::add_argument(short_arg, long_arg, help_text, *static_cast(&(*action)), required); break; case candidate_type::store_boolean: if (help_text.empty()) { auto boolPurpose = parsing_traits::parser_trait::purpose_hint; auto boolFormat = parsing_traits::parser_trait::format_hint; help_text = "Accepts " + std::string(boolPurpose) + " in " + std::string(boolFormat) + " format."; } base::add_argument(short_arg, long_arg, help_text, required); break; default: throw std::runtime_error( "Could not match the arguments against any overload. The suggested candidate was: " + std::to_string((int(suggested_add)))); } } } template void add_positional_argument_impl(ArgsMap const &argument_pairs) { std::string positional_name = get_or_throw(argument_pairs.at(add_argument_flags::Positional), "positional"); std::string help_text; std::unique_ptr action; bool required = false; std::optional position = std::nullopt; if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) { action = get_or_throw(argument_pairs.at(add_argument_flags::Action), "action").clone(); } if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) { help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); } if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { required = true; } if (argument_pairs.find(add_argument_flags::Position) != argument_pairs.end()) { position = get_or_throw(argument_pairs.at(add_argument_flags::Position), "position"); } if (help_text.empty()) { if constexpr (IsTyped) { if constexpr (internal::sfinae::has_format_hint>::value && internal::sfinae::has_purpose_hint>::value) { auto format_hint = parsing_traits::parser_trait::format_hint; auto purpose_hint = parsing_traits::parser_trait::purpose_hint; help_text = "Accepts " + std::string(purpose_hint) + " in " + std::string(format_hint) + " format."; } else { help_text = "Accepts value."; } } else { help_text = "Accepts value."; } } if constexpr (IsTyped) { if (action) { base::add_positional_argument(positional_name, help_text, *static_cast(&(*action)), required, position); } else { base::template add_positional_argument(positional_name, help_text, required, position); } } else { base::template add_positional_argument(positional_name, help_text, required, position); } } using base = argument_parser::base_parser; enum class extended_add_argument_flags { ShortArgument, LongArgument, Action, IsTyped }; enum class candidate_type { store_boolean, store_other, typed_action, non_typed_action, unknown }; template bool satisfies_at_least_one(std::array const &arr, std::unordered_map const &map) { for (const auto &req : arr) { if (map.find(req) != map.end()) return true; } return false; } candidate_type suggest_candidate(std::unordered_map const &available_vars) { auto constexpr required_at_least_one = std::array{ extended_add_argument_flags::ShortArgument, extended_add_argument_flags::LongArgument}; if (!satisfies_at_least_one(required_at_least_one, available_vars)) return candidate_type::unknown; if (available_vars.find(extended_add_argument_flags::Action) != available_vars.end()) { if (available_vars.at(extended_add_argument_flags::IsTyped)) return candidate_type::typed_action; else return candidate_type::non_typed_action; } if (available_vars.at(extended_add_argument_flags::IsTyped)) return candidate_type::store_other; return candidate_type::store_boolean; } template T get_or_throw(typed_flag_value const &v, std::string_view key) { if (auto p = std::get_if(&v)) return *p; throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); } template T get_or_throw(non_typed_flag_value const &v, std::string_view key) { if (auto p = std::get_if(&v)) return *p; throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); } }; } // namespace argument_parser::v2