#pragma once #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 { template class parametered_action { public: // Type alias to expose the parameter type, crucial for the visitor. using parameter_type = T; parametered_action(std::function const& function) : handler(function) {} void invoke(const T& arg) const { handler(arg); } private: std::function handler; }; class non_parametered_action { public: non_parametered_action(std::function const& function) : handler(function) {} void invoke() const { handler(); } private: std::function handler; }; using action_variant = std::variant< non_parametered_action, parametered_action, parametered_action, parametered_action, parametered_action >; class base_parser; class argument { public: argument() : id(0), name(), required(false), invoked(false), action(non_parametered_action([](){})) {} template argument(int id, std::string const& name, ActionType const& action) : id(id), name(name), action(action), required(false), invoked(false) {} bool is_required() const { return required; } std::string get_name() const { return name; } bool is_invoked() const { return invoked; } bool expects_parameter() const { return !std::holds_alternative(action); } 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; action_variant action; bool required; bool invoked; std::string help_text; }; 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); } 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); } std::string build_help_text(std::initializer_list convention_types) { std::stringstream ss; ss << "Usage: " << program_name << " [OPTIONS]...\n"; for (auto const& [id, arg] : argument_map) { auto short_arg = reverse_short_arguments[id]; auto long_arg = reverse_long_arguments[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); } 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); std::visit([&](auto&& action) { using ActionType = std::decay_t; if constexpr (std::is_same_v) { action.invoke(); } else { 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); using ParamType = typename ActionType::parameter_type; auto value = parsing_traits::parser_trait::parse(value_raw); action.invoke(value); } }, corresponding_argument.action); 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); } void display_help(std::initializer_list convention_types) { std::cout << build_help_text(convention_types); } protected: base_parser() = default; std::string program_name; std::vector parsed_arguments; private: 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) { if (short_arguments.count(short_arg) || long_arguments.count(long_arg)) { throw std::runtime_error("The key already exists!"); } int id = id_counter.fetch_add(1); argument arg(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; } void check_for_required_arguments(std::initializer_list convention_types) { 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); } } inline static std::atomic_int id_counter = 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; friend class linux_parser; friend class windows_parser; friend class macos_parser; friend class fake_parser; }; 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); } } } #endif // ARGUMENT_PARSER_HPP