#pragma once #include #include #include #include #include #include #include "util/uix.hpp" #include "util/pack.hpp" #include "util/function.hpp" #include "util/string_literal.hpp" #include "util/for_each.hpp" namespace ztu { static constexpr char NO_SHORT_FLAG = '\0'; namespace arx_internal { template concept parsing_function = ( ztu::callable, std::string_view> and (not std::is_class_v or std::is_empty_v) ); } // namespace arx_internal template< char ShortName, ztu::string_literal LongName, typename T = bool, auto Parse = std::nullptr_t{} > requires ( std::same_as or arx_internal::parsing_function ) struct arx_flag { static constexpr auto short_name = ShortName; static constexpr auto long_name = LongName; using type = T; static constexpr auto parse = Parse; }; namespace arx_parsers { template requires (Base > 0) [[nodiscard]] inline std::optional integer(const std::string_view& str); template [[nodiscard]] inline std::optional floating_point(const std::string_view& str); } // namespace arx_parsers namespace arx_internal { template struct flag_by_name { template struct pred : std::conditional_t< Flag::long_name == LongName, std::true_type, std::false_type > { }; using type = ztu::find; }; template using flag_by_name_t = flag_by_name::type; template using flag_type_by_name_t = flag_by_name_t::type; } // namespace arx_internal template class arx { private: static constexpr auto short_flag_prefix = std::string_view{ "-" }; static constexpr auto long_flag_prefix = std::string_view{ "--" }; static constexpr auto UNBOUND_ARGUMENT = ztu::isize_max; public: inline arx(int num_args, const char* const* args); template [[nodiscard]] inline std::optional> get() const; [[nodiscard]] inline std::optional get(ztu::isize position) const; [[nodiscard]] inline ztu::isize num_positional() const; protected: [[nodiscard]] inline std::optional find_flag_value(ztu::isize flag_index) const; template [[nodiscard]] inline std::optional> parse_value(const std::string_view& value_str) const; private: std::vector> m_arguments; ztu::isize m_unbound_begin; }; namespace arx_parsers { template requires (Base > 0) [[nodiscard]] inline std::optional integer(const std::string_view& str) { Type value{}; const auto [ptr, ec] = std::from_chars(str.begin(), str.end(), value, Base); if (ec == std::errc() and ptr == str.end()) { return value; } return std::nullopt; } template [[nodiscard]] inline std::optional floating_point(const std::string_view& str) { Type value{}; const auto [ptr, ec] = std::from_chars(str.begin(), str.end(), value, Format); if (ec == std::errc() and ptr == str.end()) { return value; } return std::nullopt; } } // namespace arx_parsers template arx::arx(int num_args, const char* const* args) { m_arguments.reserve(std::max(num_args - 1, 0)); for (int i = 1; i < num_args; i++) { const auto argument = std::string_view{ args[i] }; const auto found_match = for_each::indexed_type( [&]() { if (( Flag::short_name != NO_SHORT_FLAG and argument.length() == short_flag_prefix.length() + 1 and argument.starts_with(short_flag_prefix) and argument[short_flag_prefix.length()] == Flag::short_name ) or ( argument.length() == long_flag_prefix.length() + Flag::long_name.length() and argument.starts_with(long_flag_prefix) and argument.substr(long_flag_prefix.length()) == Flag::long_name )) { if constexpr (std::same_as) { m_arguments.emplace_back(Index, argument); } else { if (i + 1 < num_args) { const auto value = std::string_view{ args[++i] }; m_arguments.emplace_back(Index, value); } } return true; } return false; } ); if (not found_match) { m_arguments.emplace_back(UNBOUND_ARGUMENT, argument); } } std::sort( m_arguments.begin(), m_arguments.end(), [](const auto& a, const auto& b) -> bool { return a.first < b.first; } ); const auto first_unbound = std::find_if( m_arguments.begin(), m_arguments.end(), [](const auto& a) { return a.first == UNBOUND_ARGUMENT; } ); const auto num_bound = m_arguments.end() - first_unbound; const auto last = std::unique( std::reverse_iterator(first_unbound), m_arguments.rend(), [](const auto& a, const auto& b) { return a.first == b.first; } ).base(); m_arguments.erase(m_arguments.begin(), last); m_unbound_begin = m_arguments.size() - num_bound; }; template std::optional arx::find_flag_value(isize flag_index) const { const auto begin = m_arguments.begin(); const auto end = begin + m_unbound_begin; const auto it = std::lower_bound( begin, end, flag_index, [](const auto& a, const auto& b) { return a.first < b; } ); if (it != end and it->first == flag_index) { return { it->second }; } return std::nullopt; } template template std::optional> arx::parse_value( const std::string_view& value_str ) const { using Flag = arx_internal::flag_by_name_t; using Type = Flag::type; using opt_t = std::optional; opt_t ret; // Use custom parser if provided and if not try using a default parser if constexpr ( requires(const std::string_view str) { { std::invoke(Flag::parse, str) } -> std::same_as; } ) { return std::invoke(Flag::parse, value_str); } else if constexpr (std::integral && not std::same_as) { return arx_parsers::integer(value_str); } else if constexpr (std::floating_point) { return arx_parsers::floating_point(value_str); } else if constexpr (std::same_as) { return value_str; } else if constexpr (std::same_as) { return std::string(value_str); } else { Type::__cannot_parse_this_type; } return ret; } template template std::optional> arx::get() const { using Flag = arx_internal::flag_by_name_t; static constexpr auto index = index_of; if (index < sizeof...(Flags)) { const auto value_opt = find_flag_value(index); if constexpr (std::same_as) { return value_opt.has_value(); } else if (value_opt) { return parse_value(*value_opt); } } return std::nullopt; } template inline isize arx::num_positional() const { return m_arguments.size() - m_unbound_begin; } template std::optional arx::get(isize position) const { if (0 <= position and position < num_positional()) { return { m_arguments[m_unbound_begin + position].second }; } return std::nullopt; } } // namespace ztu