#pragma once #include #include #include #ifndef ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace argument_parser { class action_base { public: virtual ~action_base() = default; [[nodiscard]] virtual bool expects_parameter() const = 0; virtual void invoke() const = 0; virtual void invoke_with_parameter(const std::string& param) const = 0; [[nodiscard]] virtual std::unique_ptr clone() const = 0; }; template class parametered_action : public action_base { public: explicit parametered_action(std::function const& handler) : handler(handler) {} using parameter_type = T; void invoke(const T& arg) const { handler(arg); } [[nodiscard]] bool expects_parameter() const override { return true; } void invoke() const override { throw std::runtime_error("Parametered action requires a parameter"); } void invoke_with_parameter(const std::string& param) const override { T parsed_value = parsing_traits::parser_trait::parse(param); invoke(parsed_value); } [[nodiscard]] std::unique_ptr clone() const override { return std::make_unique>(handler); } private: std::function handler; }; class non_parametered_action : public action_base { public: explicit non_parametered_action(std::function const& handler) : handler(handler) {} void invoke() const override { handler(); } [[nodiscard]] bool expects_parameter() const override { return false; } void invoke_with_parameter(const std::string& param) const override { invoke(); } [[nodiscard]] std::unique_ptr clone() const override { return std::make_unique(handler); } private: std::function handler; }; class base_parser; class argument { public: argument() : id(0), name(), action(std::make_unique([](){})), required(false), invoked(false) {} template argument(const int id, std::string name, ActionType const& action) : id(id), name(std::move(name)), action(action.clone()), required(false), invoked(false) {} argument(const argument& other) : id(other.id), name(other.name), action(other.action->clone()), required(other.required), invoked(other.invoked), help_text(other.help_text) {} argument& operator=(const argument& other) { if (this != &other) { id = other.id; name = other.name; action = other.action->clone(); required = other.required; invoked = other.invoked; help_text = other.help_text; } return *this; } argument(argument&& other) noexcept = default; argument& operator=(argument&& other) noexcept = default; [[nodiscard]] bool is_required() const { return required; } [[nodiscard]] std::string get_name() const { return name; } [[nodiscard]] bool is_invoked() const { return invoked; } [[nodiscard]] bool expects_parameter() const { return action->expects_parameter(); } private: void set_required(bool val) { required = val; } void set_invoked(bool val) { invoked = val; } void set_help_text(std::string const& text) { help_text = text; } friend class base_parser; int id; std::string name; std::unique_ptr action; bool required; bool invoked; std::string help_text; }; namespace helpers { template static parametered_action make_parametered_action(std::function const& function) { return parametered_action(function); } static non_parametered_action make_non_parametered_action(std::function const& function) { return non_parametered_action(function); } } class base_parser { public: 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); } template void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, bool required) { base_add_argument(short_arg, long_arg, help_text, 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 add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, bool required) { base_add_argument(short_arg, long_arg, help_text, required); } void on_complete(std::function const& action) { on_complete_events.emplace_back(action); } template std::optional get_optional(std::string const& arg) const { auto id = find_argument_id(arg); if (id.has_value()) { auto value = stored_arguments.find(id.value()); if (value != stored_arguments.end() && value->second.has_value()) return std::any_cast(value->second); } return std::nullopt; } [[nodiscard]] std::string build_help_text(std::initializer_list convention_types) const { std::stringstream ss; ss << "Usage: " << program_name << " [OPTIONS]...\n"; for (auto const& [id, arg] : argument_map) { auto short_arg = reverse_short_arguments.at(id); auto long_arg = reverse_long_arguments.at(id); ss << "\t"; ss << "-" << short_arg << ", --" << long_arg; ss << "\t\t" << arg.help_text << "\n"; } return ss.str(); } 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.at(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.at(short_pos->second); } throw std::runtime_error("Unknown argument: " + arg.second); } [[nodiscard]] std::optional find_argument_id(std::string const& arg) const { auto long_pos = long_arguments.find(arg); auto short_post = short_arguments.find(arg); if (long_pos != long_arguments.end()) return long_pos->second; if (short_post != short_arguments.end()) return short_post->second; return std::nullopt; } void handle_arguments(std::initializer_list convention_types) { for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) { std::stringstream error_stream; 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) { error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n"; continue; } try { argument& corresponding_argument = get_argument(extracted); if (corresponding_argument.expects_parameter()) { if (convention_type->requires_next_token() && (it + 1) == parsed_arguments.end()) { throw std::runtime_error("expected value for argument " + extracted.second); } auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it); corresponding_argument.action->invoke_with_parameter(value_raw); } else { corresponding_argument.action->invoke(); } corresponding_argument.set_invoked(true); arg_correctly_handled = true; break; // Convention succeeded, move to the next argument token } catch (const std::runtime_error& e) { error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; } } if (!arg_correctly_handled) { throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + error_stream.str()); } } check_for_required_arguments(convention_types); fire_on_complete_events(); } void display_help(std::initializer_list convention_types) const { std::cout << build_help_text(convention_types); } protected: base_parser() = default; std::string program_name; std::vector parsed_arguments; private: void assert_argument_not_exist(std::string const& short_arg, std::string const& long_arg) const { if (short_arguments.contains(short_arg) || long_arguments.contains(long_arg)) { throw std::runtime_error("The key already exists!"); } } static void set_argument_status(bool is_required, std::string const& help_text, argument& arg) { arg.set_required(is_required); arg.set_help_text(help_text); } void place_argument(int id, argument const& arg, std::string const& short_arg, std::string const& long_arg) { 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; } template void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, ActionType const& action, bool required) { assert_argument_not_exist(short_arg, long_arg); int id = id_counter.fetch_add(1); argument arg(id, short_arg + "|" + long_arg, action); set_argument_status(required, help_text, arg); place_argument(id, arg, short_arg, long_arg); } template void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, bool required) { assert_argument_not_exist(short_arg, long_arg); int id = id_counter.fetch_add(1); if constexpr (std::is_same_v) { auto action = helpers::make_non_parametered_action([id, this] { stored_arguments[id] = std::any{ true }; }); argument arg(id, short_arg + "|" + long_arg, action); set_argument_status(required, help_text, arg); place_argument(id, arg, short_arg, long_arg); } else { auto action = helpers::make_parametered_action([id, this](StoreType const& value) { stored_arguments[id] = std::any{ value }; }); argument arg(id, short_arg + "|" + long_arg, action); set_argument_status(required, help_text, arg); place_argument(id, arg, short_arg, long_arg); } } void check_for_required_arguments(std::initializer_list convention_types) { std::vector> required_args; for (const auto &arg: argument_map | std::views::values) { 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 fire_on_complete_events() const { for(auto const& event : on_complete_events) { event(*this); } } inline static std::atomic_int id_counter = 0; std::unordered_map stored_arguments; 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::list> on_complete_events; friend class linux_parser; friend class windows_parser; friend class macos_parser; friend class fake_parser; }; } #endif // ARGUMENT_PARSER_HPP