#pragma once #include #include #include #include namespace chatterino::qmagicenum::detail { template concept DecaysTo = std::same_as, U>; template concept IsEnum = std::is_enum_v; template consteval bool isDefaultPredicate() noexcept { return std::is_same_v, std::equal_to> || std::is_same_v, std::equal_to<>>; } template consteval bool isNothrowInvocable() { return isDefaultPredicate() || std::is_nothrow_invocable_r_v; } template consteval QStringView fromArray(const std::array &arr) { return QStringView{arr.data(), static_cast(N - 1)}; } // Only the latin1 subset may be used right now, since it's easily convertible template consteval bool isLatin1(std::string_view maybe) { for (std::size_t i = 0; i < N; i++) { if (maybe[i] < 0x20 || maybe[i] > 0x7e) { return false; } } return true; } template constexpr bool eq( QStringView a, QStringView b, [[maybe_unused]] BinaryPredicate && p) noexcept(magic_enum::detail::is_nothrow_invocable()) { // Note: QStringView::operator== isn't constexpr if (a.size() != b.size()) { return false; } for (QStringView::size_type i = 0; i < a.size(); i++) { if (!p(a[i], b[i])) { return false; } } return true; } template consteval auto enumNameStorage() { constexpr auto utf8 = magic_enum::enum_name(); static_assert(isLatin1(utf8), "Can't convert non-latin1 UTF8 to UTF16"); std::array storage; for (std::size_t i = 0; i < utf8.size(); i++) { storage[i] = static_cast(utf8[i]); } storage[utf8.size()] = 0; return storage; } /// This contains a std::array for each enum value (V) with the /// corresponding name. template inline constexpr auto ENUM_NAME_STORAGE = enumNameStorage(); template consteval auto namesStorage(std::index_sequence /*unused*/) { return std::array{{detail::fromArray( ENUM_NAME_STORAGE()[I]>)...}}; } /// This contains a std::array for each enum (E). template inline constexpr auto NAMES_STORAGE = namesStorage(std::make_index_sequence()>{}); template > class CaseInsensitive { static constexpr QChar toLower(QChar c) noexcept { return (c >= u'A' && c <= u'Z') ? QChar(c.unicode() + static_cast(u'a' - u'A')) : c; } public: template L, DecaysTo R> constexpr bool operator()(L lhs, R rhs) const noexcept { return Op{}(toLower(lhs), toLower(rhs)); } }; /// @brief Gets a static QString from @a view. /// /// @pre @a view must be a static string view (i.e. it must be valid throughout /// the entire duration of the program). /// /// @param view The view to turn into a static string /// @returns Qt6: A static string (never gets freed), Qt5: regular string #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) [[nodiscard]] inline QString staticString(QStringView view) noexcept { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return QString(QStringPrivate(nullptr, const_cast(view.utf16()), view.size())); } #else [[nodiscard]] inline QString staticString(QStringView view) { return view.toString(); } #endif } // namespace chatterino::qmagicenum::detail namespace chatterino::qmagicenum { /// @brief Get the name of an enum value /// /// This version is much lighter on the compile times and is not restricted to the enum_range limitation. /// /// @tparam V The enum value /// @returns The name as a string view template [[nodiscard]] consteval QStringView enumName() noexcept { return QStringView{ detail::fromArray(detail::ENUM_NAME_STORAGE)}; } /// @brief Get the name of an enum value /// /// @param value The enum value /// @returns The name as a string view. If @a value does not have name or the /// value is out of range an empty string is returned. template [[nodiscard]] constexpr QStringView enumName(E value) noexcept { using D = std::decay_t; if (const auto i = magic_enum::enum_index(value)) { return detail::NAMES_STORAGE[*i]; } return {}; } /// @brief Get the name of an enum value /// /// This version is much lighter on the compile times and is not restricted to /// the enum_range limitation. /// /// @tparam V The enum value /// @returns The name as a string. The returned string is static. template [[nodiscard]] inline QString enumNameString() noexcept { return detail::staticString(enumName()); } /// @brief Get the name of an enum value /// /// This version is much lighter on the compile times and is not restricted to /// the enum_range limitation. /// /// @tparam V The enum value /// @returns The name as a string. If @a value does not have name or the /// value is out of range an empty string is returned. /// The returned string is static. template [[nodiscard]] inline QString enumNameString(E value) noexcept { using D = std::decay_t; return detail::staticString(enumName(value)); } /// @brief Gets the enum value from a name /// /// @tparam E The enum type to parse the @a name as /// @param name The name of the enum value to parse /// @param p A predicate to compare characters of a string /// (defaults to std::equal_to) /// @returns A `std::optional` of the parsed value. If no value was parsed, /// `std::nullopt` is returned. template > [[nodiscard]] constexpr std::optional> enumCast(QStringView name, BinaryPredicate p = {}) noexcept( detail::isNothrowInvocable()) requires std::is_invocable_r_v { using D = std::decay_t; if constexpr (magic_enum::enum_count() == 0) { static_cast(name); return std::nullopt; // Empty enum. } for (std::size_t i = 0; i < magic_enum::enum_count(); i++) { if (detail::eq(name, detail::NAMES_STORAGE[i], p)) { return magic_enum::enum_value(i); } } return std::nullopt; // Invalid value or out of range. } /// @brief Constructs a name from the @a flags /// /// @param flags The combined flags to construct the name from /// @param sep A separator between each flag (defaults to u'|') /// @returns A string containing all names separated by @a sep. If any flag in /// @a flags is out of rage or does not have a name, an empty string /// is returned. template [[nodiscard]] inline QString enumFlagsName(E flags, char16_t sep = u'|') { using D = std::decay_t; using U = std::underlying_type_t; static_assert(magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, "enumFlagsName used for non-flags enum"); QString name; auto checkValue = U{0}; for (std::size_t i = 0; i < magic_enum::enum_count(); ++i) { const auto v = static_cast(magic_enum::enum_value(i)); if ((static_cast(flags) & v) != 0) { const auto n = detail::NAMES_STORAGE[i]; if (!n.empty()) { checkValue |= v; if (!name.isEmpty()) { name.append(sep); } name.append(n); } else { return {}; // Value out of range. } } } if (checkValue != 0 && checkValue == static_cast(flags)) { return name; } return {}; // Invalid value or out of range. } /// @brief Get the names of all values from @a E. /// /// @tparam E The enum type /// @returns A `std::array` of all names (`QStringView`s) template [[nodiscard]] constexpr auto enumNames() noexcept { return detail::NAMES_STORAGE>; } /// Allows you to write qmagicenum::enumCast("bar", qmagicenum::CASE_INSENSITIVE) inline constexpr auto CASE_INSENSITIVE = detail::CaseInsensitive<>{}; } // namespace chatterino::qmagicenum