diff options
author | Treeston <treeston.mmoc@gmail.com> | 2020-08-30 02:50:25 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-02-04 21:21:17 +0100 |
commit | 45e9e943115badd1a10ce18dc660408564e4aac9 (patch) | |
tree | 3e573ba9d5da2c8eada7c71f923b5fcdc814459c | |
parent | 175fb7056b32d5455b59d83cc0f46fc55d905307 (diff) |
Core/ChatCommands: C++17 cleanup (again) (PR #25323)
(cherry picked from commit 2f7d2ef3e979ecd0536f3a3713e56c8e59652a47)
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommand.h | 259 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp | 43 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandArgs.h | 154 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandHelpers.h | 36 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandTags.h | 112 | ||||
-rw-r--r-- | src/server/game/Chat/HyperlinkTags.cpp | 159 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.cpp | 292 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.h | 145 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_debug.cpp | 4 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_message.cpp | 38 | ||||
-rw-r--r-- | tests/game/ChatCommand.cpp | 58 |
11 files changed, 655 insertions, 645 deletions
diff --git a/src/server/game/Chat/ChatCommands/ChatCommand.h b/src/server/game/Chat/ChatCommands/ChatCommand.h index c54f7f52cb5..dac914485d0 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommand.h +++ b/src/server/game/Chat/ChatCommands/ChatCommand.h @@ -33,199 +33,106 @@ class ChatHandler; class CommandArgs; -template <typename T> -struct CommandArgsConsumerSingle +namespace Trinity::Impl::ChatCommands { - using arginfo = Trinity::ChatCommands::ArgInfo<T>; - static char const* TryConsumeTo(T& val, char const* args) + template <typename T> + struct SingleConsumer { - return arginfo::TryConsume(val, args); - } -}; - -struct CommandArgsVariantConsumer -{ - template <typename V, typename T1, typename... Ts> - static char const* TryConsumeTo(V& val, char const* args) - { - T1 v; - if (char const* next = CommandArgsConsumerSingle<T1>::TryConsumeTo(v, args)) + static Optional<std::string_view> TryConsumeTo(T& val, std::string_view args) { - val = std::move(v); - return next; + return ArgInfo<T>::TryConsume(val, args); } - else if constexpr (sizeof...(Ts) > 0) - return TryConsumeTo<V, Ts...>(val, args); - else - return nullptr; - } -}; - -template <typename... Ts> -struct CommandArgsConsumerSingle<Trinity::ChatCommands::Variant<Ts...>> -{ - static char const* TryConsumeTo(Trinity::ChatCommands::Variant<Ts...>& val, char const* args) - { - return CommandArgsVariantConsumer::TryConsumeTo<Trinity::ChatCommands::Variant<Ts...>, Ts...>(val, args); - } -}; + }; -template <typename T> -struct CommandArgsConsumerSingle<std::vector<T>> -{ - static char const* TryConsumeTo(std::vector<T>& val, char const* args) + template <typename... Ts> + struct SingleConsumer<Trinity::ChatCommands::Variant<Ts...>> { - char const* last; - val.clear(); - - do val.emplace_back(); - while ((args = CommandArgsConsumerSingle<T>::TryConsumeTo(val.back(), (last = args)))); - - val.pop_back(); - return last; - } -}; + using V = std::variant<Ts...>; + static constexpr size_t N = std::variant_size_v<V>; -template <typename T, std::size_t C> -struct CommandArgsConsumerSingle<std::array<T, C>> -{ - static char const* TryConsumeTo(std::array<T, C>& val, char const* args) - { - for (T& t : val) + template <size_t I> + static Optional<std::string_view> TryAtIndex(Trinity::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] std::string_view args) { - args = CommandArgsConsumerSingle<T>::TryConsumeTo(t, args); - - if (!args) - return nullptr; + if constexpr (I < N) + { + if (Optional<std::string_view> next = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), args)) + return next; + else + return TryAtIndex<I+1>(val, args); + } + else + return std::nullopt; } - return args; - } -}; - -template <> -struct CommandArgsConsumerSingle<CommandArgs*> -{ - static char const* TryConsumeTo(CommandArgs*&, char const* args) { return args; } -}; - -template <> -struct CommandArgsConsumerSingle<char const*> -{ - static char const* TryConsumeTo(char const*&, char const* args) { return args; } -}; - -template <typename T, size_t offset> -struct CommandArgsConsumerNext; - -template <typename Tuple, typename NextType, size_t offset> -struct CommandArgsConsumerMulti -{ - static char const* TryConsumeTo(Tuple& tuple, char const* args) - { - if (char const* next = CommandArgsConsumerSingle<NextType>::TryConsumeTo(std::get<offset>(tuple), args)) - return CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, next); - else - return nullptr; - } -}; - -template <typename Tuple, typename NestedNextType, size_t offset> -struct CommandArgsConsumerMulti<Tuple, Optional<NestedNextType>, offset> -{ - static char const* TryConsumeTo(Tuple& tuple, char const* args) + static Optional<std::string_view> TryConsumeTo(Trinity::ChatCommands::Variant<Ts...>& val, std::string_view args) + { + return TryAtIndex<0>(val, args); + } + }; + + /* + for backwards compatibility, consumes the rest of the string + new code should use the Tail/WTail tags defined in ChatCommandTags + */ + template <> + struct SingleConsumer<char const*> { - // try with the argument - auto& myArg = std::get<offset>(tuple); - myArg.emplace(); - if (char const* next = CommandArgsConsumerSingle<NestedNextType>::TryConsumeTo(myArg.value(), args)) - if ((next = CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, next))) - return next; - // try again omitting the argument - myArg = std::nullopt; - if (char const* next = CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, args)) - return next; - return nullptr; - } -}; + static Optional<std::string_view> TryConsumeTo(char const*& arg, std::string_view args) { arg = args.data(); return std::string_view(); } + }; -template <size_t offset, typename... Ts> -struct CommandArgsConsumerNext<std::tuple<Ts...>, offset> -{ - using tuple_type = std::tuple<Ts...>; - template <bool C = (offset < sizeof...(Ts))> - static std::enable_if_t<C, char const*> GoNext(tuple_type& tuple, char const* args) - { - return CommandArgsConsumerMulti<tuple_type, std::tuple_element_t<offset, tuple_type>, offset>::TryConsumeTo(tuple, args); - } + // forward declaration + // ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer + // the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc + // MultiConsumer calls SingleConsumer in each iteration + template <typename Tuple, size_t offset> + Optional<std::string_view> ConsumeFromOffset(Tuple&, std::string_view args); - template <bool C = (offset < sizeof...(Ts))> - static std::enable_if_t<!C, char const*> GoNext(tuple_type&, char const* args) + template <typename Tuple, typename NextType, size_t offset> + struct MultiConsumer { - return args; - } -}; - -class TC_GAME_API CommandArgs -{ - public: - CommandArgs(char const* args) : _original(args), _args(args) {} - - template <typename T1, typename T2, typename... Ts> - auto TryConsume() + static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args) { - Optional<std::tuple<advstd::remove_cvref_t<T1>, advstd::remove_cvref_t<T2>, advstd::remove_cvref_t<Ts>...>> rv; - rv.emplace(); - if (!TryConsumeToTuple<0>(rv.value())) - rv = std::nullopt; - return rv; - } - - template <typename T1> - auto TryConsume() - { - using T = advstd::remove_cvref_t<T1>; - Optional<T> rv; - rv.emplace(); - if (char const* next = CommandArgsConsumerSingle<T>::TryConsumeTo(rv.value(), _args)) - _args = next; + if (Optional<std::string_view> next = SingleConsumer<NextType>::TryConsumeTo(std::get<offset>(tuple), args)) + return ConsumeFromOffset<Tuple, offset + 1>(tuple, *next); else - rv = std::nullopt; - return rv; + return std::nullopt; } + }; - template <size_t offset = 0, typename T> - bool TryConsumeToTuple(T& tuple) + template <typename Tuple, typename NestedNextType, size_t offset> + struct MultiConsumer<Tuple, Optional<NestedNextType>, offset> + { + static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args) { - if (char const* next = CommandArgsConsumerNext<T, offset>::GoNext(tuple, _args)) - { - _args = next; - return true; - } - else - return false; + // try with the argument + auto& myArg = std::get<offset>(tuple); + myArg.emplace(); + if (Optional<std::string_view> next = SingleConsumer<NestedNextType>::TryConsumeTo(myArg.value(), args)) + if ((next = ConsumeFromOffset<Tuple, offset + 1>(tuple, *next))) + return next; + // try again omitting the argument + myArg = std::nullopt; + return ConsumeFromOffset<Tuple, offset + 1>(tuple, args); } + }; - void Reset() { _args = _original; } - - char const* GetFullArgs() const { return _original; } - char const* GetRemainingArgs() const { return _args; } - - bool IsEmpty() const { return !!*_args; } - explicit operator bool() const { return IsEmpty(); } - - private: - char const* const _original; - char const* _args; -}; + template <typename Tuple, size_t offset> + Optional<std::string_view> ConsumeFromOffset(Tuple& tuple, std::string_view args) + { + if constexpr (offset < std::tuple_size_v<Tuple>) + return MultiConsumer<Tuple, std::tuple_element_t<offset, Tuple>, offset>::TryConsumeTo(tuple, args); + else if (!args.empty()) /* the entire string must be consumed */ + return std::nullopt; + else + return args; + } -template <typename T> struct ChatCommandHandlerToTuple { static_assert(!std::is_same_v<T,T>, "Invalid command handler signature"); }; -template <typename... Ts> struct ChatCommandHandlerToTuple<bool(*)(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; }; + template <typename T> struct HandlerToTuple { static_assert(Trinity::dependant_false_v<T>, "Invalid command handler signature"); }; + template <typename... Ts> struct HandlerToTuple<bool(*)(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; }; + template <typename T> using TupleType = typename HandlerToTuple<T>::type; +} -template <typename T> struct ChatCommandStoreLastArg { static void store(T&, CommandArgs&) {} }; -template <> struct ChatCommandStoreLastArg<char const*> { static void store(char const*& arg, CommandArgs& args) { arg = args.GetRemainingArgs(); } }; -template <> struct ChatCommandStoreLastArg<CommandArgs*> { static void store(CommandArgs*& arg, CommandArgs& args) { arg = &args; } }; class TC_GAME_API ChatCommand { @@ -238,18 +145,12 @@ class TC_GAME_API ChatCommand { _wrapper = [](void* handler, ChatHandler* chatHandler, char const* argsStr) { - using tuple_type = typename ChatCommandHandlerToTuple<TypedHandler>::type; + using Tuple = Trinity::Impl::ChatCommands::TupleType<TypedHandler>; - tuple_type arguments; + Tuple arguments; std::get<0>(arguments) = chatHandler; - - CommandArgs args(argsStr); - if (args.TryConsumeToTuple<1>(arguments)) - { - auto& last = std::get<std::tuple_size_v<tuple_type>-1>(arguments); - ChatCommandStoreLastArg<advstd::remove_cvref_t<decltype(last)>>::store(last, args); + if (Trinity::Impl::ChatCommands::ConsumeFromOffset<Tuple, 1>(arguments, argsStr)) return std::apply(reinterpret_cast<TypedHandler>(handler), std::move(arguments)); - } else return false; }; @@ -272,7 +173,7 @@ class TC_GAME_API ChatCommand bool HasHandler() const { return !!_handler; } char const* Name; - uint32 Permission; // function pointer required correct align (use uint32) + uint32 Permission; bool AllowConsole; std::string Help; std::vector<ChatCommand> ChildCommands; diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp index 9f62839d612..df74ac40e26 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp @@ -20,6 +20,7 @@ #include "DB2Stores.h" #include "ObjectMgr.h" #include "SpellMgr.h" +#include "Util.h" using namespace Trinity::ChatCommands; @@ -29,12 +30,13 @@ struct AchievementVisitor value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; } value_type operator()(uint32 achId) const { return sAchievementStore.LookupEntry(achId); } }; -char const* Trinity::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, char const* args) +Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, std::string_view args) { Variant<Hyperlink<achievement>, uint32> val; - if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) + Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); + if (next) data = val.visit(AchievementVisitor()); - return args; + return next; } struct CurrencyTypesVisitor @@ -43,10 +45,11 @@ struct CurrencyTypesVisitor value_type operator()(Hyperlink<currency> currency) const { return currency->Currency; } value_type operator()(uint32 currencyId) const { return sCurrencyTypesStore.LookupEntry(currencyId); } }; -char const* Trinity::ChatCommands::ArgInfo<CurrencyTypesEntry const*>::TryConsume(CurrencyTypesEntry const*& data, char const* args) +Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<CurrencyTypesEntry const*>::TryConsume(CurrencyTypesEntry const*& data, std::string_view args) { Variant<Hyperlink<currency>, uint32> val; - if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) + Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); + if (next) data = val.visit(CurrencyTypesVisitor()); return args; } @@ -57,12 +60,13 @@ struct GameTeleVisitor value_type operator()(Hyperlink<tele> tele) const { return sObjectMgr->GetGameTele(tele); } value_type operator()(std::string const& tele) const { return sObjectMgr->GetGameTele(tele); } }; -char const* Trinity::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, char const* args) +Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, std::string_view args) { Variant<Hyperlink<tele>, std::string> val; - if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) + Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); + if (next) data = val.visit(GameTeleVisitor()); - return args; + return next; } struct SpellInfoVisitor @@ -80,26 +84,11 @@ struct SpellInfoVisitor value_type operator()(uint32 spellId) const { return sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); } }; -char const* Trinity::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, char const* args) +Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, std::string_view args) { Variant<Hyperlink<apower>, Hyperlink<conduit>, Hyperlink<enchant>, Hyperlink<mawpower>, Hyperlink<pvptal>, Hyperlink<spell>, Hyperlink<talent>, Hyperlink<trade>, uint32> val; - if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) + Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); + if (next) data = val.visit(SpellInfoVisitor()); - return args; -} - -char const* Trinity::ChatCommands::ArgInfo<bool>::TryConsume(bool& data, char const* args) -{ - std::string val; - if ((args = CommandArgsConsumerSingle<std::string>::TryConsumeTo(val, args))) - { - strToLower(val); - if (val == "on" || val == "yes" || val == "true" || val == "1" || val == "y") - data = true; - else if (val == "off" || val == "no" || val == "false" || val == "0" || val == "n") - data = false; - else - return nullptr; - } - return args; + return next; } diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h index e2270c0259f..22e96b1ccda 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h @@ -30,17 +30,15 @@ struct GameTele; -namespace Trinity -{ -namespace ChatCommands +namespace Trinity::Impl::ChatCommands { /************************** ARGUMENT HANDLERS *******************************************\ |* Define how to extract contents of a certain requested type from a string *| |* Must implement the following: *| -|* - TryConsume: T&, char const* -> char const* *| -|* returns nullptr if no match, otherwise pointer to first character of next token *| -|* - if nullptr is returned, state of T& is indeterminate *| +|* - TryConsume: T&, std::string_view -> Optional<std::string_view> *| +|* returns nullopt if no match, otherwise tail of argument string *| +|* - if nullopt is returned, state of T& is indeterminate *| |* - otherwise, T& should be initialized to the intended return value *| |* *| \****************************************************************************************/ @@ -51,16 +49,17 @@ struct ArgInfo { static_assert(!std::is_same_v<T,T>, "Invalid command parameter template <typename T> struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>> { - static char const* TryConsume(T& val, char const* args) + static Optional<std::string_view> TryConsume(T& val, std::string_view args) { - char const* next = args; - std::string_view token(args, Trinity::Impl::ChatCommands::tokenize(next)); + auto [token, tail] = tokenize(args); + if (token.empty()) + return std::nullopt; if (Optional<T> v = StringTo<T>(token, 0)) val = *v; else - return nullptr; - return next; + return std::nullopt; + return tail; } }; @@ -68,20 +67,26 @@ struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>> template <typename T> struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>> { - static char const* TryConsume(T& val, char const* args) + static Optional<std::string_view> TryConsume(T& val, std::string_view args) { - char const* next = args; - std::string token(args, Trinity::Impl::ChatCommands::tokenize(next)); + auto [token, tail] = tokenize(args); + if (token.empty()) + return std::nullopt; + try { // @todo replace this once libc++ supports double args to from_chars for required minimum size_t processedChars = 0; - val = std::stold(token, &processedChars); + val = std::stold(std::string(token), &processedChars); if (processedChars != token.length()) - return nullptr; + return std::nullopt; } - catch (...) { return nullptr; } - return std::isfinite(val) ? next : nullptr; + catch (...) { return std::nullopt; } + + if (std::isfinite(val)) + return tail; + else + return std::nullopt; } }; @@ -89,16 +94,13 @@ struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>> template <> struct ArgInfo<std::string_view, void> { - static char const* TryConsume(std::string_view& val, char const* args) + static Optional<std::string_view> TryConsume(std::string_view& val, std::string_view args) { - char const* next = args; - if (size_t len = Trinity::Impl::ChatCommands::tokenize(next)) - { - val = std::string_view(args, len); - return next; - } - else - return nullptr; + auto [token, next] = tokenize(args); + if (token.empty()) + return std::nullopt; + val = token; + return next; } }; @@ -106,13 +108,13 @@ struct ArgInfo<std::string_view, void> template <> struct ArgInfo<std::string, void> { - static char const* TryConsume(std::string& val, char const* args) + static Optional<std::string_view> TryConsume(std::string& val, std::string_view args) { std::string_view view; - args = ArgInfo<std::string_view>::TryConsume(view, args); - if (args) + Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(view, args); + if (next) val.assign(view); - return args; + return next; } }; @@ -120,18 +122,15 @@ struct ArgInfo<std::string, void> template <> struct ArgInfo<std::wstring, void> { - static char const* TryConsume(std::wstring& val, char const* args) + static Optional<std::string_view> TryConsume(std::wstring& val, std::string_view args) { std::string_view utf8view; - char const* ret = ArgInfo<std::string_view>::TryConsume(utf8view, args); - - if (!ret) - return nullptr; - - if (!Utf8toWStr(utf8view, val)) - return nullptr; + Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(utf8view, args); - return ret; + if (next && Utf8toWStr(utf8view, val)) + return next; + else + return std::nullopt; } }; @@ -189,32 +188,31 @@ struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>> return nullptr; } - static char const* TryConsume(T& val, char const* args) + static Optional<std::string_view> TryConsume(T& val, std::string_view args) { std::string strVal; - char const* ret = ArgInfo<std::string>::TryConsume(strVal, args); - - if (!ret) - return nullptr; + Optional<std::string_view> next = ArgInfo<std::string>::TryConsume(strVal, args); - if (T const* tmpVal = Match(strVal)) + if (next) { - val = *tmpVal; - return ret; + if (T const* match = Match(strVal)) + { + val = *match; + return next; + } } // Value not found. Try to parse arg as underlying type and cast it to enum type using U = std::underlying_type_t<T>; U uVal = 0; - ret = ArgInfo<U>::TryConsume(uVal, args); - if (ret) + next = ArgInfo<U>::TryConsume(uVal, args); + if (next && EnumUtils::IsValid<T>(uVal)) { val = static_cast<T>(uVal); - if (!EnumUtils::IsValid(val)) - return nullptr; + return next; } - return ret; + return std::nullopt; } }; @@ -222,48 +220,72 @@ struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>> template <typename T> struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> { - static char const* TryConsume(T& tag, char const* args) + static Optional<std::string_view> TryConsume(T& tag, std::string_view args) { return tag.TryConsume(args); } }; +template <typename T> +struct ArgInfo<std::vector<T>, void> +{ + static Optional<std::string_view> TryConsume(std::vector<T>& val, std::string_view args) + { + val.clear(); + Optional<std::string_view> next = ArgInfo<T>::TryConsume(val.emplace_back(), args); + + if (!next) + return std::nullopt; + + while (Optional<std::string_view> next2 = ArgInfo<T>::TryConsume(val.emplace_back(), *next)) + next = next2; + + val.pop_back(); + return next; + } +}; + +template <typename T, size_t N> +struct ArgInfo<std::array<T, N>, void> +{ + static Optional<std::string_view> TryConsume(std::array<T, N>& val, std::string_view args) + { + Optional<std::string_view> next = args; + for (T& t : val) + if (!(next = ArgInfo<T>::TryConsume(t, *next))) + return std::nullopt; + return next; + } +}; + // AchievementEntry* from numeric id or link template <> struct TC_GAME_API ArgInfo<AchievementEntry const*> { - static char const* TryConsume(AchievementEntry const*&, char const*); + static Optional<std::string_view> TryConsume(AchievementEntry const*&, std::string_view); }; // CurrencyTypesEntry* from numeric id or link template <> struct TC_GAME_API ArgInfo<CurrencyTypesEntry const*> { - static char const* TryConsume(CurrencyTypesEntry const*&, char const*); + static Optional<std::string_view> TryConsume(CurrencyTypesEntry const*&, std::string_view); }; // GameTele* from string name or link template <> struct TC_GAME_API ArgInfo<GameTele const*> { - static char const* TryConsume(GameTele const*&, char const*); + static Optional<std::string_view> TryConsume(GameTele const*&, std::string_view); }; // SpellInfo const* from spell id or link template <> struct TC_GAME_API ArgInfo<SpellInfo const*> { - static char const* TryConsume(SpellInfo const*&, char const*); + static Optional<std::string_view> TryConsume(SpellInfo const*&, std::string_view); }; -// bool from 1/0 or on/off -template <> -struct TC_GAME_API ArgInfo<bool> -{ - static char const* TryConsume(bool&, char const*); -}; - -} } #endif diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h index e22823e5d35..c6cf4f8ad94 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h @@ -18,10 +18,15 @@ #ifndef TRINITY_CHATCOMMANDHELPERS_H #define TRINITY_CHATCOMMANDHELPERS_H +#include <string_view> #include <type_traits> -namespace Trinity::ChatCommands +namespace Trinity::Impl::ChatCommands { + /***************** HELPERS *************************\ + |* These really aren't for outside use... *| + \***************************************************/ + static constexpr char COMMAND_DELIMITER = ' '; template <typename T, typename = void> @@ -32,19 +37,26 @@ namespace Trinity::ChatCommands template <typename T> using tag_base_t = typename tag_base<T>::type; -} -namespace Trinity::Impl::ChatCommands -{ - /***************** HELPERS *************************\ - |* These really aren't for outside use... *| - \***************************************************/ - inline std::size_t tokenize(char const*& end) + struct TokenizeResult { + explicit operator bool() { return !token.empty(); } + std::string_view token; + std::string_view tail; + }; + + inline TokenizeResult tokenize(std::string_view args) { - std::size_t len = 0; - for (; *end && *end != Trinity::ChatCommands::COMMAND_DELIMITER; ++end, ++len); - for (; *end && *end == Trinity::ChatCommands::COMMAND_DELIMITER; ++end); - return len; + TokenizeResult result; + if (size_t delimPos = args.find(COMMAND_DELIMITER); delimPos != std::string_view::npos) + { + result.token = args.substr(0, delimPos); + if (size_t tailPos = args.find_first_not_of(COMMAND_DELIMITER, delimPos); tailPos != std::string_view::npos) + result.tail = args.substr(tailPos); + } + else + result.token = args; + + return result; } template <typename T, typename... Ts> diff --git a/src/server/game/Chat/ChatCommands/ChatCommandTags.h b/src/server/game/Chat/ChatCommands/ChatCommandTags.h index 78f49673794..9255ef18fd3 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandTags.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandTags.h @@ -22,59 +22,95 @@ #include "ChatCommandHelpers.h" #include "Hyperlinks.h" #include "Optional.h" +#include "Util.h" #include <cmath> #include <cstring> #include <iostream> #include <string> +#include <string_view> #include <tuple> #include <type_traits> #include <utility> #include <variant> +namespace Trinity::Impl::ChatCommands +{ + struct ContainerTag {}; + template <typename T> + struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> + { + using type = typename T::value_type; + }; +} + namespace Trinity::ChatCommands { /************************** CONTAINER TAGS **********************************************\ |* Simple holder classes to differentiate between extraction methods *| - |* Should inherit from ContainerTag for template identification *| + |* Must inherit from Trinity::Impl::ChatCommands::ContainerTag *| |* Must implement the following: *| - |* - TryConsume: char const* -> char const* *| - |* returns nullptr if no match, otherwise pointer to first character of next token *| + |* - TryConsume: std::string_view -> Optional<std::string_view> *| + |* returns nullopt if no match, otherwise the tail of the provided argument string *| |* - typedef value_type of type that is contained within the tag *| |* - cast operator to value_type *| |* *| \****************************************************************************************/ - struct ContainerTag {}; - template <typename T> - struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> - { - using type = typename T::value_type; - }; template <char c1, char... chars> - struct ExactSequence : public ContainerTag + struct ExactSequence : Trinity::Impl::ChatCommands::ContainerTag { using value_type = void; - static char const* _TryConsume(char const* pos) + static constexpr size_t N = (sizeof...(chars) + 1); + + static bool Match(char const* pos) + { + if (*(pos++) != c1) + return false; + else if constexpr (sizeof...(chars) > 0) + return ExactSequence<chars...>::Match(pos); + else + return true; + } + + Optional<std::string_view> TryConsume(std::string_view args) const { - if (*(pos++) == c1) + if ((N <= args.length()) && ExactSequence::Match(args.data())) { - if constexpr (sizeof...(chars) > 0) - return ExactSequence<chars...>::_TryConsume(pos); - else if (Trinity::Impl::ChatCommands::tokenize(pos)) /* we did not consume the entire token */ - return nullptr; - else - return pos; + auto [remainingToken, tail] = Trinity::Impl::ChatCommands::tokenize(args.substr(N)); + if (remainingToken.empty()) // if this is not empty, then we did not consume the full token + return tail; } - else - return nullptr; + return std::nullopt; + } + }; + + struct Tail : std::string_view, Trinity::Impl::ChatCommands::ContainerTag + { + using value_type = std::string_view; + + Optional<std::string_view> TryConsume(std::string_view args) + { + std::string_view::operator=(args); + return std::string_view(); } + }; - char const* TryConsume(char const* pos) const { return ExactSequence::_TryConsume(pos); } + struct WTail : std::wstring, Trinity::Impl::ChatCommands::ContainerTag + { + using value_type = std::wstring; + + Optional<std::string_view> TryConsume(std::string_view args) + { + if (Utf8toWStr(args, *this)) + return std::string_view(); + else + return std::nullopt; + } }; template <typename linktag> - struct Hyperlink : public ContainerTag + struct Hyperlink : Trinity::Impl::ChatCommands::ContainerTag { using value_type = typename linktag::value_type; using storage_type = advstd::remove_cvref_t<value_type>; @@ -84,29 +120,27 @@ namespace Trinity::ChatCommands value_type operator*() const { return val; } storage_type const* operator->() const { return &val; } - char const* TryConsume(char const* pos) + Optional<std::string_view> TryConsume(std::string_view args) { - Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseHyperlink(pos); + Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseSingleHyperlink(args); // invalid hyperlinks cannot be consumed if (!info) - return nullptr; + return std::nullopt; // check if we got the right tag - if (info.tag.second != strlen(linktag::tag())) - return nullptr; - if (strncmp(info.tag.first, linktag::tag(), strlen(linktag::tag())) != 0) - return nullptr; + if (info.tag != linktag::tag()) + return std::nullopt; // store value - if (!linktag::StoreTo(val, info.data.first, info.data.second)) - return nullptr; + if (!linktag::StoreTo(val, info.data)) + return std::nullopt; - // finally, skip to end of token - pos = info.next; - Trinity::Impl::ChatCommands::tokenize(pos); - - // return final pos - return pos; + // finally, skip any potential delimiters + auto [token, next] = Trinity::Impl::ChatCommands::tokenize(info.tail); + if (token.empty()) /* empty token = first character is delimiter, skip past it */ + return next; + else + return info.tail; } private: @@ -138,8 +172,8 @@ namespace Trinity::ChatCommands { using base = std::variant<T1, Ts...>; - using first_type = tag_base_t<T1>; - static constexpr bool have_operators = Trinity::Impl::ChatCommands::are_all_assignable<first_type, tag_base_t<Ts>...>::value; + using first_type = Trinity::Impl::ChatCommands::tag_base_t<T1>; + static constexpr bool have_operators = Trinity::Impl::ChatCommands::are_all_assignable<first_type, Trinity::Impl::ChatCommands::tag_base_t<Ts>...>::value; template <bool C = have_operators> std::enable_if_t<C, first_type> operator*() const diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp index 05a48ad65b4..f0d36065170 100644 --- a/src/server/game/Chat/HyperlinkTags.cpp +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -27,44 +27,50 @@ static constexpr char HYPERLINK_DATA_DELIMITER = ':'; class HyperlinkDataTokenizer { public: - HyperlinkDataTokenizer(char const* pos, size_t len, bool allowEmptyTokens = false) : _pos(pos), _len(len), _allowEmptyTokens(allowEmptyTokens), _empty(false) {} + HyperlinkDataTokenizer(std::string_view str, bool allowEmptyTokens = false) : _str(str), _allowEmptyTokens(allowEmptyTokens) {} - template <typename T> - bool TryConsumeTo(T& val) - { - if (_empty) - return false; - - char const* firstPos = _pos; - size_t thisLen = 0; - // find next delimiter - for (; _len && *_pos != HYPERLINK_DATA_DELIMITER; --_len, ++_pos, ++thisLen); - if (_len) - --_len, ++_pos; // skip the delimiter - else - _empty = true; - - if (_allowEmptyTokens && !thisLen) + template <typename T> + bool TryConsumeTo(T& val) { - val = T(); + if (IsEmpty()) + { + if (_allowEmptyTokens) + { + val = T(); + return true; + } + return false; + } + + if (size_t off = _str.find(HYPERLINK_DATA_DELIMITER); off != std::string_view::npos) + { + if (!Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str.substr(0, off))) + { + if (off != 0 || !_allowEmptyTokens) + return false; + val = T(); + } + _str = _str.substr(off+1); + } + else + { + if (!Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str)) + return false; + _str = std::string_view(); + } return true; } - return Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, firstPos, thisLen); - } - - bool IsEmpty() { return _empty; } + bool IsEmpty() const { return _str.empty(); } private: - char const* _pos; - size_t _len; - bool _allowEmptyTokens; - bool _empty; + std::string_view _str; + bool _allowEmptyTokens; }; -bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 achievementId; if (!t.TryConsumeTo(achievementId)) return false; @@ -74,9 +80,9 @@ bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& va t.TryConsumeTo(val.Criteria[1]) && t.TryConsumeTo(val.Criteria[2]) && t.TryConsumeTo(val.Criteria[3]) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::apower::StoreTo(ArtifactPowerLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::apower::StoreTo(ArtifactPowerLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 artifactPowerId; if (!(t.TryConsumeTo(artifactPowerId) && t.TryConsumeTo(val.PurchasedRank) && t.TryConsumeTo(val.CurrentRankWithBonus) && t.IsEmpty())) return false; @@ -88,9 +94,9 @@ bool Trinity::Hyperlinks::LinkTags::apower::StoreTo(ArtifactPowerLinkData& val, return true; } -bool Trinity::Hyperlinks::LinkTags::azessence::StoreTo(AzeriteEssenceLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::azessence::StoreTo(AzeriteEssenceLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 azeriteEssenceId; if (!t.TryConsumeTo(azeriteEssenceId)) return false; @@ -98,30 +104,31 @@ bool Trinity::Hyperlinks::LinkTags::azessence::StoreTo(AzeriteEssenceLinkData& v && sDB2Manager.GetAzeriteEssencePower(azeriteEssenceId, val.Rank) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::battlepet::StoreTo(BattlePetLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::battlepet::StoreTo(BattlePetLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 battlePetSpeciesId; if (!t.TryConsumeTo(battlePetSpeciesId)) return false; return (val.Species = sBattlePetSpeciesStore.LookupEntry(battlePetSpeciesId)) && t.TryConsumeTo(val.Level) && t.TryConsumeTo(val.Quality) && val.Quality < MAX_ITEM_QUALITY && t.TryConsumeTo(val.MaxHealth) && t.TryConsumeTo(val.Power) && t.TryConsumeTo(val.Speed) - && t.TryConsumeTo(val.PetGuid) && val.PetGuid.GetHigh() == HighGuid::BattlePet && t.TryConsumeTo(val.DisplayId); + && t.TryConsumeTo(val.PetGuid) && val.PetGuid.GetHigh() == HighGuid::BattlePet && t.TryConsumeTo(val.DisplayId) + && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::conduit::StoreTo(SoulbindConduitRankEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::conduit::StoreTo(SoulbindConduitRankEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 soulbindConduitId, rank; if (!(t.TryConsumeTo(soulbindConduitId) && t.TryConsumeTo(rank) && t.IsEmpty())) return false; return !!(val = sDB2Manager.GetSoulbindConduitRank(soulbindConduitId, rank)); } -bool Trinity::Hyperlinks::LinkTags::currency::StoreTo(CurrencyLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::currency::StoreTo(CurrencyLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 currencyId; if (!t.TryConsumeTo(currencyId)) return false; @@ -132,18 +139,18 @@ bool Trinity::Hyperlinks::LinkTags::currency::StoreTo(CurrencyLinkData& val, cha return true; } -bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 spellId; if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) return false; return !!(val = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL); } -bool Trinity::Hyperlinks::LinkTags::garrfollower::StoreTo(GarrisonFollowerLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::garrfollower::StoreTo(GarrisonFollowerLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 garrFollowerId; if (!t.TryConsumeTo(garrFollowerId)) return false; @@ -152,7 +159,7 @@ bool Trinity::Hyperlinks::LinkTags::garrfollower::StoreTo(GarrisonFollowerLinkDa if (!val.Follower || !t.TryConsumeTo(val.Quality) || val.Quality >= MAX_ITEM_QUALITY || !t.TryConsumeTo(val.Level) || !t.TryConsumeTo(val.ItemLevel) || !t.TryConsumeTo(val.Abilities[0]) || !t.TryConsumeTo(val.Abilities[1]) || !t.TryConsumeTo(val.Abilities[2]) || !t.TryConsumeTo(val.Abilities[3]) || !t.TryConsumeTo(val.Traits[0]) || !t.TryConsumeTo(val.Traits[1]) || !t.TryConsumeTo(val.Traits[2]) || !t.TryConsumeTo(val.Traits[3]) - || !t.TryConsumeTo(val.Specialization)) + || !t.TryConsumeTo(val.Specialization) || !t.IsEmpty()) return false; for (uint32 ability : val.Abilities) @@ -169,27 +176,27 @@ bool Trinity::Hyperlinks::LinkTags::garrfollower::StoreTo(GarrisonFollowerLinkDa return true; } -bool Trinity::Hyperlinks::LinkTags::garrfollowerability::StoreTo(GarrAbilityEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::garrfollowerability::StoreTo(GarrAbilityEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 garrAbilityId; if (!t.TryConsumeTo(garrAbilityId)) return false; return !!(val = sGarrAbilityStore.LookupEntry(garrAbilityId)) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::garrmission::StoreTo(GarrisonMissionLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::garrmission::StoreTo(GarrisonMissionLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 garrMissionId; if (!t.TryConsumeTo(garrMissionId)) return false; return !!(val.Mission = sGarrMissionStore.LookupEntry(garrMissionId)) && t.TryConsumeTo(val.DbID) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::instancelock::StoreTo(InstanceLockLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::instancelock::StoreTo(InstanceLockLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); if (!t.TryConsumeTo(val.Owner)) return false; uint32 mapId; @@ -200,9 +207,9 @@ bool Trinity::Hyperlinks::LinkTags::instancelock::StoreTo(InstanceLockLinkData& && t.TryConsumeTo(val.CompletedEncountersMask) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len, true); + HyperlinkDataTokenizer t(text, true); uint32 itemId, dummy, numBonusListIDs; if (!t.TryConsumeTo(itemId)) return false; @@ -260,9 +267,9 @@ bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* return t.TryConsumeTo(val.Creator) && t.TryConsumeTo(val.UseEnchantId) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::journal::StoreTo(JournalLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::journal::StoreTo(JournalLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 id; if (!t.TryConsumeTo(val.Type) || !t.TryConsumeTo(id) || !t.TryConsumeTo(val.Difficulty) || !t.IsEmpty()) return false; @@ -306,9 +313,9 @@ bool Trinity::Hyperlinks::LinkTags::journal::StoreTo(JournalLinkData& val, char return true; } -bool Trinity::Hyperlinks::LinkTags::keystone::StoreTo(KeystoneLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::keystone::StoreTo(KeystoneLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 mapChallengeModeId; if (!t.TryConsumeTo(val.ItemId) || !t.TryConsumeTo(mapChallengeModeId) || !t.TryConsumeTo(val.Level) || !t.TryConsumeTo(val.Affix[0]) || !t.TryConsumeTo(val.Affix[1]) || !t.TryConsumeTo(val.Affix[2]) || !t.TryConsumeTo(val.Affix[3]) @@ -326,18 +333,18 @@ bool Trinity::Hyperlinks::LinkTags::keystone::StoreTo(KeystoneLinkData& val, cha return true; } -bool Trinity::Hyperlinks::LinkTags::mawpower::StoreTo(MawPowerEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::mawpower::StoreTo(MawPowerEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 mawPowerId; if (!t.TryConsumeTo(mawPowerId)) return false; return !!(val = sMawPowerStore.LookupEntry(mawPowerId)) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::pvptal::StoreTo(PvpTalentEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::pvptal::StoreTo(PvpTalentEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 pvpTalentId; if (!(t.TryConsumeTo(pvpTalentId) && t.IsEmpty())) return false; @@ -346,18 +353,18 @@ bool Trinity::Hyperlinks::LinkTags::pvptal::StoreTo(PvpTalentEntry const*& val, return true; } -bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 questId; if (!t.TryConsumeTo(questId)) return false; return (val.Quest = sObjectMgr->GetQuestTemplate(questId)) && t.TryConsumeTo(val.ContentTuningId) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 spellId, glyphPropertiesId; if (!(t.TryConsumeTo(spellId) && t.TryConsumeTo(glyphPropertiesId) && t.IsEmpty())) return false; @@ -365,9 +372,9 @@ bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellLinkData& val, char cons && (!glyphPropertiesId || !!(val.Glyph = sGlyphPropertiesStore.LookupEntry(glyphPropertiesId))); } -bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 talentId; if (!(t.TryConsumeTo(talentId) && t.IsEmpty())) return false; @@ -376,9 +383,9 @@ bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentEntry const*& val, cha return true; } -bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 spellId, skillId; if (!t.TryConsumeTo(val.Owner) || !t.TryConsumeTo(spellId) || !t.TryConsumeTo(skillId) || !t.IsEmpty()) return false; @@ -389,18 +396,18 @@ bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char return true; } -bool Trinity::Hyperlinks::LinkTags::transmogappearance::StoreTo(ItemModifiedAppearanceEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::transmogappearance::StoreTo(ItemModifiedAppearanceEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 itemModifiedAppearanceId; if (!t.TryConsumeTo(itemModifiedAppearanceId)) return false; return !!(val = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId)) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::transmogillusion::StoreTo(SpellItemEnchantmentEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::transmogillusion::StoreTo(SpellItemEnchantmentEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 spellItemEnchantmentId; if (!t.TryConsumeTo(spellItemEnchantmentId)) return false; @@ -408,18 +415,18 @@ bool Trinity::Hyperlinks::LinkTags::transmogillusion::StoreTo(SpellItemEnchantme && sDB2Manager.GetTransmogIllusionForEnchantment(spellItemEnchantmentId) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::transmogset::StoreTo(TransmogSetEntry const*& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::transmogset::StoreTo(TransmogSetEntry const*& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 transmogSetId; if (!t.TryConsumeTo(transmogSetId)) return false; return !!(val = sTransmogSetStore.LookupEntry(transmogSetId)) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::worldmap::StoreTo(WorldMapLinkData& val, char const* pos, size_t len) +bool Trinity::Hyperlinks::LinkTags::worldmap::StoreTo(WorldMapLinkData& val, std::string_view text) { - HyperlinkDataTokenizer t(pos, len); + HyperlinkDataTokenizer t(text); uint32 uiMapId; if (!t.TryConsumeTo(uiMapId)) return false; diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp index 88a4dcfae2b..387e723ce11 100644 --- a/src/server/game/Chat/Hyperlinks.cpp +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -33,87 +33,95 @@ using namespace Trinity::Hyperlinks; inline uint8 toHex(char c) { return (c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00; } // Validates a single hyperlink -HyperlinkInfo Trinity::Hyperlinks::ParseHyperlink(char const* pos) +HyperlinkInfo Trinity::Hyperlinks::ParseSingleHyperlink(std::string_view str) { - //color tag - if (*(pos++) != '|' || *(pos++) != 'c') - return nullptr; uint32 color = 0; + std::string_view tag; + std::string_view data; + std::string_view text; + + //color tag + if (str.substr(0, 2) != "|c") + return {}; + str.remove_prefix(2); + + if (str.length() < 8) + return {}; + for (uint8 i = 0; i < 8; ++i) { - if (uint8 hex = toHex(*(pos++))) + if (uint8 hex = toHex(str[i])) color = (color << 4) | (hex & 0xf); else - return nullptr; - } - // link data start tag - if (*(pos++) != '|' || *(pos++) != 'H') - return nullptr; - // link tag, find next : or | - char const* tagStart = pos; - size_t tagLength = 0; - while (*pos && *pos != '|' && *(pos++) != ':') // we only advance pointer to one past if the last thing is : (not for |), this is intentional! - ++tagLength; - // ok, link data, skip to next | - char const* dataStart = pos; - size_t dataLength = 0; - while (*pos && *(pos++) != '|') - ++dataLength; + return {}; + } + str.remove_prefix(8); + + if (str.substr(0, 2) != "|H") + return {}; + str.remove_prefix(2); + + // tag+data part follows + if (size_t delimPos = str.find('|'); delimPos != std::string_view::npos) + { + tag = str.substr(0, delimPos); + str.remove_prefix(delimPos+1); + } + else + return {}; + + // split tag if : is present (data separator) + if (size_t dataStart = tag.find(':'); dataStart != std::string_view::npos) + { + data = tag.substr(dataStart+1); + tag = tag.substr(0, dataStart); + } + // ok, next should be link data end tag... - if (*(pos++) != 'h') - return nullptr; - // then visible link text, starts with [ - if (*(pos++) != '[') - return nullptr; - // skip until we hit the next ], abort on unexpected | - char const* textStart = pos; - size_t textLength = 0; - while (*pos) - { - if (*pos == '|') - return nullptr; - if (*(pos++) == ']') - break; - ++textLength; - } - // link end tag - if (*(pos++) != '|' || *(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r') - return nullptr; + if (str.substr(0, 1) != "h") + return {}; + str.remove_prefix(1); + // skip to final | + if (size_t end = str.find('|'); end != std::string_view::npos) + { + // check end tag + if (str.substr(end, 4) != "|h|r") + return {}; + // check text brackets + if ((str[0] != '[') || (str[end - 1] != ']')) + return {}; + text = str.substr(1, end - 2); + // tail + str = str.substr(end + 4); + } + else + return {}; + // ok, valid hyperlink, return info - return { pos, color, tagStart, tagLength, dataStart, dataLength, textStart, textLength }; + return { str, color, tag, data, text }; } template <typename T> struct LinkValidator { - static bool IsTextValid(typename T::value_type, char const*, size_t) { return true; } + static bool IsTextValid(typename T::value_type, std::string_view) { return true; } static bool IsColorValid(typename T::value_type, HyperlinkColor) { return true; } }; -// str1 is null-terminated, str2 is length-terminated, check if they are exactly equal -static bool equal_with_len(char const* str1, char const* str2, size_t len) -{ - if (!*str1) - return false; - while (len && *str1 && *(str1++) == *(str2++)) - --len; - return !len && !*str1; -} - -static bool IsCreatureNameValid(uint32 creatureId, char const* pos, size_t len) +static bool IsCreatureNameValid(uint32 creatureId, std::string_view text) { if (CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureId)) { CreatureLocale const* locale = sObjectMgr->GetCreatureLocale(creatureId); if (!locale) - return equal_with_len(creatureTemplate->Name.c_str(), pos, len); + return creatureTemplate->Name == text; for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { std::string const& name = (i == DEFAULT_LOCALE) ? creatureTemplate->Name : locale->Name[i]; if (name.empty()) continue; - if (equal_with_len(name.c_str(), pos, len)) + if (name == text) return true; } } @@ -124,15 +132,15 @@ static bool IsCreatureNameValid(uint32 creatureId, char const* pos, size_t len) template <> struct LinkValidator<LinkTags::spell> { - static bool IsTextValid(SpellLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(SpellLinkData const& data, std::string_view text) { - return IsTextValid(data.Spell, pos, len); + return IsTextValid(data.Spell, text); } - static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) + static bool IsTextValid(SpellInfo const* info, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len((*info->SpellName)[i], pos, len)) + if ((*info->SpellName)[i] == text) return true; return false; } @@ -146,12 +154,12 @@ struct LinkValidator<LinkTags::spell> template <> struct LinkValidator<LinkTags::achievement> { - static bool IsTextValid(AchievementLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(AchievementLinkData const& data, std::string_view text) { - if (!len) + if (text.empty()) return false; for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(data.Achievement->Title[i], pos, len)) + if (text == data.Achievement->Title[i]) return true; return false; } @@ -165,10 +173,10 @@ struct LinkValidator<LinkTags::achievement> template <> struct LinkValidator<LinkTags::apower> { - static bool IsTextValid(ArtifactPowerLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(ArtifactPowerLinkData const& data, std::string_view text) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.ArtifactPower->SpellID, DIFFICULTY_NONE)) - return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); return false; } @@ -181,10 +189,10 @@ struct LinkValidator<LinkTags::apower> template <> struct LinkValidator<LinkTags::azessence> { - static bool IsTextValid(AzeriteEssenceLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(AzeriteEssenceLinkData const& data, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(data.Essence->Name[i], pos, len)) + if (data.Essence->Name[i] == text) return true; return false; } @@ -198,9 +206,9 @@ struct LinkValidator<LinkTags::azessence> template <> struct LinkValidator<LinkTags::battlepet> { - static bool IsTextValid(BattlePetLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(BattlePetLinkData const& data, std::string_view text) { - return IsCreatureNameValid(data.Species->CreatureID, pos, len); + return IsCreatureNameValid(data.Species->CreatureID, text); } static bool IsColorValid(BattlePetLinkData const& data, HyperlinkColor c) @@ -212,10 +220,10 @@ struct LinkValidator<LinkTags::battlepet> template <> struct LinkValidator<LinkTags::conduit> { - static bool IsTextValid(SoulbindConduitRankEntry const* rank, char const* pos, size_t len) + static bool IsTextValid(SoulbindConduitRankEntry const* rank, std::string_view text) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(rank->SpellID, DIFFICULTY_NONE)) - return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); return false; } @@ -228,11 +236,11 @@ struct LinkValidator<LinkTags::conduit> template <> struct LinkValidator<LinkTags::currency> { - static bool IsTextValid(CurrencyLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(CurrencyLinkData const& data, std::string_view text) { LocalizedString const* name = data.Container ? &data.Container->ContainerName : &data.Currency->Name; for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len((*name)[i], pos, len)) + if ((*name)[i] == text) return true; return false; } @@ -246,9 +254,9 @@ struct LinkValidator<LinkTags::currency> template <> struct LinkValidator<LinkTags::enchant> { - static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) + static bool IsTextValid(SpellInfo const* info, std::string_view text) { - if (LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len)) + if (LinkValidator<LinkTags::spell>::IsTextValid(info, text)) return true; SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id); if (bounds.first == bounds.second) @@ -264,11 +272,12 @@ struct LinkValidator<LinkTags::enchant> for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) { - char const* skillName = skill->DisplayName[i]; - size_t skillLen = strlen(skillName); - if (len > skillLen + 2 && // or of form [Skill Name: Spell Name] - !strncmp(pos, skillName, skillLen) && !strncmp(pos + skillLen, ": ", 2) && - equal_with_len((*info->SpellName)[i], pos + (skillLen + 2), len - (skillLen + 2))) + std::string_view skillName = skill->DisplayName[i]; + std::string_view spellName = (*info->SpellName)[i]; + if ((text.length() == (skillName.length() + 2 + spellName.length())) && + (text.substr(0, skillName.length()) == skillName) && + (text.substr(skillName.length(), 2) == ": ") && + (text.substr(skillName.length() + 2) == spellName)) return true; } } @@ -284,10 +293,10 @@ struct LinkValidator<LinkTags::enchant> template <> struct LinkValidator<LinkTags::garrfollower> { - static bool IsTextValid(GarrisonFollowerLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(GarrisonFollowerLinkData const& data, std::string_view text) { - return IsCreatureNameValid(data.Follower->HordeCreatureID, pos, len) - || IsCreatureNameValid(data.Follower->AllianceCreatureID, pos, len); + return IsCreatureNameValid(data.Follower->HordeCreatureID, text) + || IsCreatureNameValid(data.Follower->AllianceCreatureID, text); } static bool IsColorValid(GarrisonFollowerLinkData const& data, HyperlinkColor c) @@ -299,10 +308,10 @@ struct LinkValidator<LinkTags::garrfollower> template <> struct LinkValidator<LinkTags::garrfollowerability> { - static bool IsTextValid(GarrAbilityEntry const* ability, char const* pos, size_t len) + static bool IsTextValid(GarrAbilityEntry const* ability, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(ability->Name[i], pos, len)) + if (ability->Name[i] == text) return true; return false; } @@ -316,10 +325,10 @@ struct LinkValidator<LinkTags::garrfollowerability> template <> struct LinkValidator<LinkTags::garrmission> { - static bool IsTextValid(GarrisonMissionLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(GarrisonMissionLinkData const& data, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(data.Mission->Name[i], pos, len)) + if (data.Mission->Name[i] == text) return true; return false; } @@ -333,10 +342,10 @@ struct LinkValidator<LinkTags::garrmission> template <> struct LinkValidator<LinkTags::instancelock> { - static bool IsTextValid(InstanceLockLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(InstanceLockLinkData const& data, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(data.Map->MapName[i], pos, len)) + if (data.Map->MapName[i] == text) return true; return false; } @@ -350,16 +359,16 @@ struct LinkValidator<LinkTags::instancelock> template <> struct LinkValidator<LinkTags::item> { - static bool IsTextValid(ItemLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(ItemLinkData const& data, std::string_view text) { LocalizedString const* suffixStrings = nullptr; if (!data.Item->HasFlag(ITEM_FLAG3_HIDE_NAME_SUFFIX) && data.Suffix) suffixStrings = &data.Suffix->Description; - return IsTextValid(data.Item, suffixStrings, pos, len); + return IsTextValid(data.Item, suffixStrings, text); } - static bool IsTextValid(ItemTemplate const* itemTemplate, LocalizedString const* suffixStrings, char const* pos, size_t len) + static bool IsTextValid(ItemTemplate const* itemTemplate, LocalizedString const* suffixStrings, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) { @@ -368,13 +377,17 @@ struct LinkValidator<LinkTags::item> continue; if (suffixStrings) { - if (len > name.length() + 1 && - (strncmp(name.c_str(), pos, name.length()) == 0) && - (*(pos + name.length()) == ' ') && - equal_with_len((*suffixStrings)[i], pos + name.length() + 1, len - name.length() - 1)) + std::string_view suffix = (*suffixStrings)[i]; + if ( + (!suffix.empty()) && + (text.length() == (name.length() + 1 + suffix.length())) && + (text.substr(0, name.length()) == name) && + (text[name.length()] == ' ') && + (text.substr(name.length() + 1) == suffix) + ) return true; } - else if (equal_with_len(name.c_str(), pos, len)) + else if (text == name) return true; } return false; @@ -389,10 +402,10 @@ struct LinkValidator<LinkTags::item> template <> struct LinkValidator<LinkTags::journal> { - static bool IsTextValid(JournalLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(JournalLinkData const& data, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len((*data.ExpectedText)[i], pos, len)) + if ((*data.ExpectedText)[i] == text) return true; return false; } @@ -406,21 +419,20 @@ struct LinkValidator<LinkTags::journal> template <> struct LinkValidator<LinkTags::keystone> { - static bool IsTextValid(KeystoneLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(KeystoneLinkData const& data, std::string_view text) { // Skip "Keystone" prefix - not loading GlobalStrings.db2 - char const* validateStartPos = strstr(pos, ": "); - if (!validateStartPos) + size_t validateStartPos = text.find(": "); + if (validateStartPos == std::string_view::npos) return false; - // skip ": " too - validateStartPos += 2; - size_t validateLen = len - (validateStartPos - pos); + text.remove_prefix(validateStartPos); + text.remove_prefix(2); // skip ": " too for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) { std::string expectedText = Trinity::StringFormat("%s (%u)", data.Map->Name[i], data.Level); - if (equal_with_len(expectedText.c_str(), validateStartPos, validateLen)) + if (expectedText == text) return true; } return false; @@ -435,18 +447,18 @@ struct LinkValidator<LinkTags::keystone> template <> struct LinkValidator<LinkTags::quest> { - static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(QuestLinkData const& data, std::string_view text) { QuestTemplateLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId()); if (!locale) - return equal_with_len(data.Quest->GetLogTitle().c_str(), pos, len); + return data.Quest->GetLogTitle().c_str() == text; for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { std::string const& name = (i == DEFAULT_LOCALE) ? data.Quest->GetLogTitle() : locale->LogTitle[i]; if (name.empty()) continue; - if (equal_with_len(name.c_str(), pos, len)) + if (text == name) return true; } @@ -465,10 +477,10 @@ struct LinkValidator<LinkTags::quest> template <> struct LinkValidator<LinkTags::mawpower> { - static bool IsTextValid(MawPowerEntry const* mawPower, char const* pos, size_t len) + static bool IsTextValid(MawPowerEntry const* mawPower, std::string_view text) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(mawPower->SpellID, DIFFICULTY_NONE)) - return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); return false; } @@ -481,7 +493,7 @@ struct LinkValidator<LinkTags::mawpower> template <> struct LinkValidator<LinkTags::outfit> { - static bool IsTextValid(std::string const&, char const*, size_t) + static bool IsTextValid(std::string const&, std::string_view) { return true; } @@ -495,10 +507,10 @@ struct LinkValidator<LinkTags::outfit> template <> struct LinkValidator<LinkTags::pvptal> { - static bool IsTextValid(PvpTalentEntry const* pvpTalent, char const* pos, size_t len) + static bool IsTextValid(PvpTalentEntry const* pvpTalent, std::string_view text) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(pvpTalent->SpellID, DIFFICULTY_NONE)) - return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); return false; } @@ -511,10 +523,10 @@ struct LinkValidator<LinkTags::pvptal> template <> struct LinkValidator<LinkTags::talent> { - static bool IsTextValid(TalentEntry const* talent, char const* pos, size_t len) + static bool IsTextValid(TalentEntry const* talent, std::string_view text) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE)) - return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); return false; } @@ -527,9 +539,9 @@ struct LinkValidator<LinkTags::talent> template <> struct LinkValidator<LinkTags::trade> { - static bool IsTextValid(TradeskillLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(TradeskillLinkData const& data, std::string_view text) { - return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, pos, len); + return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, text); } static bool IsColorValid(TradeskillLinkData const&, HyperlinkColor c) @@ -541,10 +553,10 @@ struct LinkValidator<LinkTags::trade> template <> struct LinkValidator<LinkTags::transmogappearance> { - static bool IsTextValid(ItemModifiedAppearanceEntry const* enchantment, char const* pos, size_t len) + static bool IsTextValid(ItemModifiedAppearanceEntry const* enchantment, std::string_view text) { if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(enchantment->ItemID)) - return LinkValidator<LinkTags::item>::IsTextValid(itemTemplate, nullptr, pos, len); + return LinkValidator<LinkTags::item>::IsTextValid(itemTemplate, nullptr, text); return false; } @@ -557,13 +569,13 @@ struct LinkValidator<LinkTags::transmogappearance> template <> struct LinkValidator<LinkTags::transmogillusion> { - static bool IsTextValid(SpellItemEnchantmentEntry const* enchantment, char const* pos, size_t len) + static bool IsTextValid(SpellItemEnchantmentEntry const* enchantment, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(enchantment->Name[i], pos, len)) + if (enchantment->Name[i] == text) return true; for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len(enchantment->HordeName[i], pos, len)) + if (enchantment->HordeName[i] == text) return true; return false; } @@ -577,17 +589,17 @@ struct LinkValidator<LinkTags::transmogillusion> template <> struct LinkValidator<LinkTags::transmogset> { - static bool IsTextValid(TransmogSetEntry const* set, char const* pos, size_t len) + static bool IsTextValid(TransmogSetEntry const* set, std::string_view text) { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) { if (ItemNameDescriptionEntry const* itemNameDescription = sItemNameDescriptionStore.LookupEntry(set->ItemNameDescriptionID)) { std::string expectedText = Trinity::StringFormat("%s (%s)", set->Name[i], itemNameDescription->Description[i]); - if (equal_with_len(expectedText.c_str(), pos, len)) + if (expectedText.c_str() == text) return true; } - else if (equal_with_len(set->Name[i], pos, len)) + else if (set->Name[i] == text) return true; } return false; @@ -602,7 +614,7 @@ struct LinkValidator<LinkTags::transmogset> template <> struct LinkValidator<LinkTags::worldmap> { - static bool IsTextValid(WorldMapLinkData const&, char const*, size_t) + static bool IsTextValid(WorldMapLinkData const&, std::string_view) { return true; } @@ -615,17 +627,16 @@ struct LinkValidator<LinkTags::worldmap> #define TryValidateAs(tagname) \ { \ - ASSERT(!strcmp(LinkTags::tagname::tag(), #tagname)); \ - if (info.tag.second == strlen(LinkTags::tagname::tag()) && \ - !strncmp(info.tag.first, LinkTags::tagname::tag(), strlen(LinkTags::tagname::tag()))) \ + static_assert(LinkTags::tagname::tag() == #tagname); \ + if (info.tag == LinkTags::tagname::tag()) \ { \ advstd::remove_cvref_t<typename LinkTags::tagname::value_type> t; \ - if (!LinkTags::tagname::StoreTo(t, info.data.first, info.data.second)) \ + if (!LinkTags::tagname::StoreTo(t, info.data)) \ return false; \ if (!LinkValidator<LinkTags::tagname>::IsColorValid(t, info.color)) \ return false; \ if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)) \ - if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text.first, info.text.second)) \ + if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text)) \ return false; \ return true; \ } \ @@ -675,16 +686,19 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info) } // Validates all hyperlinks and control sequences contained in str -bool Trinity::Hyperlinks::CheckAllLinks(std::string const& str) +bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str) { // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r { - std::string::size_type pos = 0; + std::string_view::size_type pos = 0; while ((pos = str.find('|', pos)) != std::string::npos) { - char next = str[pos + 1]; + ++pos; + if (pos == str.length()) + return false; + char next = str[pos]; if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|') - pos += 2; + ++pos; else return false; } @@ -697,21 +711,21 @@ bool Trinity::Hyperlinks::CheckAllLinks(std::string const& str) // - <linkdata> is arbitrary length, no | contained // - <linktext> is printable { - std::string::size_type pos = 0; - while ((pos = str.find('|', pos)) != std::string::npos) + std::string::size_type pos; + while ((pos = str.find('|')) != std::string::npos) { if (str[pos + 1] == '|') // this is an escaped pipe character (||) { - pos += 2; + str = str.substr(pos + 2); continue; } - HyperlinkInfo info = ParseHyperlink(str.c_str() + pos); + HyperlinkInfo info = ParseSingleHyperlink(str.substr(pos)); if (!info || !ValidateLinkInfo(info)) return false; // tag is fine, find the next one - pos = info.next - str.c_str(); + str = info.tail; } } diff --git a/src/server/game/Chat/Hyperlinks.h b/src/server/game/Chat/Hyperlinks.h index 2085a27c4e5..dfc3f656ccf 100644 --- a/src/server/game/Chat/Hyperlinks.h +++ b/src/server/game/Chat/Hyperlinks.h @@ -21,6 +21,7 @@ #include "ObjectGuid.h" #include "StringConvert.h" #include <string> +#include <string_view> #include <type_traits> #include <utility> @@ -204,26 +205,32 @@ namespace Trinity::Hyperlinks |* Link tags must abide by the following: *| |* - MUST expose ::value_type typedef *| |* - storage type is remove_cvref_t<value_type> *| - |* - MUST expose static ::tag method, void -> const char* *| + |* - MUST expose static ::tag method, void -> std::string_view *| |* - this method SHOULD be constexpr *| |* - returns identifier string for the link ("creature", "creature_entry", "item") *| - |* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *| - |* - assign value_type& based on content of std::string(char const*, size_t) *| + |* - MUST expose static ::StoreTo method, (storage&, std::string_view) *| + |* - assign value_type& based on content of std::string_view *| |* - return value indicates success/failure *| |* - for integral/string types this can be achieved by extending base_tag *| \****************************************************************************************/ struct base_tag { - static bool StoreTo(std::string& val, char const* pos, size_t len) + static bool StoreTo(std::string_view& val, std::string_view data) { - val.assign(pos, len); + val = data; + return true; + } + + static bool StoreTo(std::string& val, std::string_view data) + { + val.assign(data); return true; } template <typename T> - static std::enable_if_t<std::is_integral_v<T>, bool> StoreTo(T& val, char const* pos, size_t len) + static std::enable_if_t<std::is_integral_v<T>, bool> StoreTo(T& val, std::string_view data) { - if (Optional<T> res = Trinity::StringTo<T>(std::string_view(pos, len))) + if (Optional<T> res = Trinity::StringTo<T>(data)) { val = *res; return true; @@ -232,9 +239,9 @@ namespace Trinity::Hyperlinks return false; } - static bool StoreTo(ObjectGuid& val, char const* pos, size_t len) + static bool StoreTo(ObjectGuid& val, std::string_view data) { - ObjectGuid parsed = ObjectGuid::FromString(std::string(pos, len)); + ObjectGuid parsed = ObjectGuid::FromString(std::string(data)); if (parsed != ObjectGuid::FromStringFailed) { val = parsed; @@ -245,7 +252,7 @@ namespace Trinity::Hyperlinks } }; - #define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } } + #define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr std::string_view tag() { return #ltag; } } // custom formats make_base_tag(area, uint32); make_base_tag(areatrigger, uint32); @@ -268,176 +275,176 @@ namespace Trinity::Hyperlinks struct TC_GAME_API achievement { using value_type = AchievementLinkData const&; - static constexpr char const* tag() { return "achievement"; } - static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "achievement"; } + static bool StoreTo(AchievementLinkData& val, std::string_view text); }; struct TC_GAME_API apower { using value_type = ArtifactPowerLinkData const&; - static constexpr char const* tag() { return "apower"; } - static bool StoreTo(ArtifactPowerLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "apower"; } + static bool StoreTo(ArtifactPowerLinkData& val, std::string_view text); }; struct TC_GAME_API azessence { using value_type = AzeriteEssenceLinkData const&; - static constexpr char const* tag() { return "azessence"; } - static bool StoreTo(AzeriteEssenceLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "azessence"; } + static bool StoreTo(AzeriteEssenceLinkData& val, std::string_view text); }; struct TC_GAME_API battlepet { using value_type = BattlePetLinkData const&; - static constexpr char const* tag() { return "battlepet"; } - static bool StoreTo(BattlePetLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "battlepet"; } + static bool StoreTo(BattlePetLinkData& val, std::string_view text); }; struct TC_GAME_API conduit { using value_type = SoulbindConduitRankEntry const*; - static constexpr char const* tag() { return "conduit"; } - static bool StoreTo(SoulbindConduitRankEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "conduit"; } + static bool StoreTo(SoulbindConduitRankEntry const*& val, std::string_view text); }; struct TC_GAME_API currency { using value_type = CurrencyLinkData const&; - static constexpr char const* tag() { return "currency"; } - static bool StoreTo(CurrencyLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "currency"; } + static bool StoreTo(CurrencyLinkData& val, std::string_view text); }; struct TC_GAME_API enchant { using value_type = SpellInfo const*; - static constexpr char const* tag() { return "enchant"; } - static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "enchant"; } + static bool StoreTo(SpellInfo const*& val, std::string_view text); }; struct TC_GAME_API garrfollower { using value_type = GarrisonFollowerLinkData const&; - static constexpr char const* tag() { return "garrfollower"; } - static bool StoreTo(GarrisonFollowerLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "garrfollower"; } + static bool StoreTo(GarrisonFollowerLinkData& val, std::string_view text); }; struct TC_GAME_API garrfollowerability { using value_type = GarrAbilityEntry const*; - static constexpr char const* tag() { return "garrfollowerability"; } - static bool StoreTo(GarrAbilityEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "garrfollowerability"; } + static bool StoreTo(GarrAbilityEntry const*& val, std::string_view text); }; struct TC_GAME_API garrmission { using value_type = GarrisonMissionLinkData const&; - static constexpr char const* tag() { return "garrmission"; } - static bool StoreTo(GarrisonMissionLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "garrmission"; } + static bool StoreTo(GarrisonMissionLinkData& val, std::string_view text); }; struct TC_GAME_API instancelock { using value_type = InstanceLockLinkData const&; - static constexpr char const* tag() { return "instancelock"; } - static bool StoreTo(InstanceLockLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "instancelock"; } + static bool StoreTo(InstanceLockLinkData& val, std::string_view text); }; struct TC_GAME_API item { using value_type = ItemLinkData const&; - static constexpr char const* tag() { return "item"; } - static bool StoreTo(ItemLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "item"; } + static bool StoreTo(ItemLinkData& val, std::string_view text); }; struct TC_GAME_API journal { using value_type = JournalLinkData const&; - static constexpr char const* tag() { return "journal"; } - static bool StoreTo(JournalLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "journal"; } + static bool StoreTo(JournalLinkData& val, std::string_view text); }; struct TC_GAME_API keystone { using value_type = KeystoneLinkData const&; - static constexpr char const* tag() { return "keystone"; } - static bool StoreTo(KeystoneLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "keystone"; } + static bool StoreTo(KeystoneLinkData& val, std::string_view text); }; struct TC_GAME_API mawpower { using value_type = MawPowerEntry const*; - static constexpr char const* tag() { return "mawpower"; } - static bool StoreTo(MawPowerEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "mawpower"; } + static bool StoreTo(MawPowerEntry const*& val, std::string_view text); }; struct TC_GAME_API pvptal { using value_type = PvpTalentEntry const*; - static constexpr char const* tag() { return "pvptal"; } - static bool StoreTo(PvpTalentEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "pvptal"; } + static bool StoreTo(PvpTalentEntry const*& val, std::string_view text); }; struct TC_GAME_API quest { using value_type = QuestLinkData const&; - static constexpr char const* tag() { return "quest"; } - static bool StoreTo(QuestLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "quest"; } + static bool StoreTo(QuestLinkData& val, std::string_view text); }; struct TC_GAME_API spell { using value_type = SpellLinkData const&; - static constexpr char const* tag() { return "spell"; } - static bool StoreTo(SpellLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "spell"; } + static bool StoreTo(SpellLinkData& val, std::string_view text); }; struct TC_GAME_API talent { using value_type = TalentEntry const*; - static constexpr char const* tag() { return "talent"; } - static bool StoreTo(TalentEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "talent"; } + static bool StoreTo(TalentEntry const*& val, std::string_view text); }; struct TC_GAME_API trade { using value_type = TradeskillLinkData const&; - static constexpr char const* tag() { return "trade"; } - static bool StoreTo(TradeskillLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "trade"; } + static bool StoreTo(TradeskillLinkData& val, std::string_view text); }; struct TC_GAME_API transmogappearance { using value_type = ItemModifiedAppearanceEntry const*; - static constexpr char const* tag() { return "transmogappearance"; } - static bool StoreTo(ItemModifiedAppearanceEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "transmogappearance"; } + static bool StoreTo(ItemModifiedAppearanceEntry const*& val, std::string_view text); }; struct TC_GAME_API transmogillusion { using value_type = SpellItemEnchantmentEntry const*; - static constexpr char const* tag() { return "transmogillusion"; } - static bool StoreTo(SpellItemEnchantmentEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "transmogillusion"; } + static bool StoreTo(SpellItemEnchantmentEntry const*& val, std::string_view text); }; struct TC_GAME_API transmogset { using value_type = TransmogSetEntry const*; - static constexpr char const* tag() { return "transmogset"; } - static bool StoreTo(TransmogSetEntry const*& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "transmogset"; } + static bool StoreTo(TransmogSetEntry const*& val, std::string_view text); }; struct TC_GAME_API worldmap { using value_type = WorldMapLinkData const&; - static constexpr char const* tag() { return "worldmap"; } - static bool StoreTo(WorldMapLinkData& val, char const* pos, size_t len); + static constexpr std::string_view tag() { return "worldmap"; } + static bool StoreTo(WorldMapLinkData& val, std::string_view text); }; } struct HyperlinkColor { HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {} - uint8 r, g, b, a; + uint8 const r, g, b, a; bool operator==(uint32 c) const { if ((c & 0xff) ^ b) @@ -454,18 +461,20 @@ namespace Trinity::Hyperlinks struct HyperlinkInfo { - HyperlinkInfo(char const* n = nullptr, uint32 c = 0, char const* tS = nullptr, size_t tL = 0, char const* dS = nullptr, size_t dL = 0, char const* cS = nullptr, size_t cL = 0) : - next(n), color(c), tag(tS, tL), data(dS, dL), text(cS, cL) {} + HyperlinkInfo() : ok(false), color(0) {} + HyperlinkInfo(std::string_view t, uint32 c, std::string_view ta, std::string_view d, std::string_view te) : + ok(true), tail(t), color(c), tag(ta), data(d), text(te) {} - explicit operator bool() { return next; } - char const* const next; + explicit operator bool() { return ok; } + bool const ok; + std::string_view const tail; HyperlinkColor const color; - std::pair<char const*, size_t> const tag; - std::pair<char const*, size_t> const data; - std::pair<char const*, size_t> const text; + std::string_view const tag; + std::string_view const data; + std::string_view const text; }; - HyperlinkInfo TC_GAME_API ParseHyperlink(char const* pos); - bool TC_GAME_API CheckAllLinks(std::string const&); + HyperlinkInfo TC_GAME_API ParseSingleHyperlink(std::string_view str); + bool TC_GAME_API CheckAllLinks(std::string_view str); } diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 1e9c07ca554..ce9d0952f73 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -1681,7 +1681,7 @@ public: return true; }; - static bool HandleDebugOutOfBounds([[maybe_unused]] ChatHandler* handler, CommandArgs* /*args*/) + static bool HandleDebugOutOfBounds([[maybe_unused]] ChatHandler* handler) { #ifdef ASAN uint8 stack_array[10] = {}; @@ -1838,7 +1838,7 @@ public: return true; } - static bool HandleDebugDummyCommand(ChatHandler* handler, CommandArgs* /*args*/) + static bool HandleDebugDummyCommand(ChatHandler* handler) { handler->SendSysMessage("This command does nothing right now. Edit your local core (cs_debug.cpp) to make it do whatever you need for testing."); return true; diff --git a/src/server/scripts/Commands/cs_message.cpp b/src/server/scripts/Commands/cs_message.cpp index 8fbf85fda83..157961594ab 100644 --- a/src/server/scripts/Commands/cs_message.cpp +++ b/src/server/scripts/Commands/cs_message.cpp @@ -130,62 +130,60 @@ public: return true; } - static bool HandleNameAnnounceCommand(ChatHandler* handler, CommandArgs* args) + static bool HandleNameAnnounceCommand(ChatHandler* handler, Tail message) { - if (!*args) + if (message.empty()) return false; std::string name("Console"); if (WorldSession* session = handler->GetSession()) name = session->GetPlayer()->GetName(); - sWorld->SendWorldText(LANG_ANNOUNCE_COLOR, name.c_str(), args->GetFullArgs()); + sWorld->SendWorldText(LANG_ANNOUNCE_COLOR, name.c_str(), message.data()); return true; } - static bool HandleGMNameAnnounceCommand(ChatHandler* handler, CommandArgs* args) + static bool HandleGMNameAnnounceCommand(ChatHandler* handler, Tail message) { - if (!*args) + if (message.empty()) return false; std::string name("Console"); if (WorldSession* session = handler->GetSession()) name = session->GetPlayer()->GetName(); - sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), args->GetFullArgs()); + sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), message.data()); return true; } // global announce - static bool HandleAnnounceCommand(ChatHandler* handler, char const* args) + static bool HandleAnnounceCommand(ChatHandler* handler, Tail message) { - if (!*args) + if (message.empty()) return false; - std::string str = handler->PGetParseString(LANG_SYSTEMMESSAGE, args); - - sWorld->SendServerMessage(SERVER_MSG_STRING, str); + sWorld->SendServerMessage(SERVER_MSG_STRING, Trinity::StringFormat(handler->GetTrinityString(LANG_SYSTEMMESSAGE), message.data()).c_str()); return true; } // announce to logged in GMs - static bool HandleGMAnnounceCommand(ChatHandler* /*handler*/, CommandArgs* args) + static bool HandleGMAnnounceCommand(ChatHandler* /*handler*/, Tail message) { - if (!*args) + if (message.empty()) return false; - sWorld->SendGMText(LANG_GM_BROADCAST, args->GetFullArgs()); + sWorld->SendGMText(LANG_GM_BROADCAST, message.data()); return true; } // send on-screen notification to players - static bool HandleNotifyCommand(ChatHandler* handler, CommandArgs* args) + static bool HandleNotifyCommand(ChatHandler* handler, Tail message) { - if (!*args) + if (message.empty()) return false; std::string str = handler->GetTrinityString(LANG_GLOBAL_NOTIFY); - str += args->GetFullArgs(); + str += message; sWorld->SendGlobalMessage(WorldPackets::Chat::PrintNotification(str).Write()); @@ -193,13 +191,13 @@ public: } // send on-screen notification to GMs - static bool HandleGMNotifyCommand(ChatHandler* handler, CommandArgs* args) + static bool HandleGMNotifyCommand(ChatHandler* handler, Tail message) { - if (!*args) + if (message.empty()) return false; std::string str = handler->GetTrinityString(LANG_GM_NOTIFY); - str += args->GetFullArgs(); + str += message; sWorld->SendGlobalGMMessage(WorldPackets::Chat::PrintNotification(str).Write()); diff --git a/tests/game/ChatCommand.cpp b/tests/game/ChatCommand.cpp index 5aab641deba..f484b5806c5 100644 --- a/tests/game/ChatCommand.cpp +++ b/tests/game/ChatCommand.cpp @@ -27,7 +27,7 @@ static void TestChatCommand(char const* c, F f, Optional<bool> expected = true) { bool r = ChatCommand("", 0, false, +f, "")(nullptr, c); if (expected) - ASSERT(r == *expected); + REQUIRE(r == *expected); } TEST_CASE("Command return pass-through", "[ChatCommand]") @@ -38,27 +38,51 @@ TEST_CASE("Command return pass-through", "[ChatCommand]") TEST_CASE("Command argument parsing", "[ChatCommand]") { - TestChatCommand("42", [](ChatHandler*, uint32 u) + SECTION("Single uint32 argument") { - REQUIRE(u == 42); - return true; - }); + TestChatCommand("42", [](ChatHandler*, uint32 u) + { + REQUIRE(u == 42); + return true; + }); + TestChatCommand("true", [](ChatHandler*, uint32) { return true; }, false); + } - TestChatCommand("true", [](ChatHandler*, uint32) { return true; }, false); + SECTION("std::vector<uint8>") + { + TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint8> v) + { + REQUIRE(v.size() == 10); + for (size_t i = 0; i < 10; ++i) + REQUIRE(v[i] == (i + 1)); + return true; + }); + } - TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint8> v) + SECTION("Hyperlink<player>") { - REQUIRE(v.size() == 10); - for (size_t i = 0; i < 10; ++i) - REQUIRE(v[i] == (i + 1)); - return true; - }); + TestChatCommand("|cffff0000|Hplayer:Test|h[Test]|h|r", + [](ChatHandler*, Hyperlink<player> player) + { + REQUIRE("Test"sv == *player); + return true; + } + ); + } - TestChatCommand("|cffff0000|Hplayer:Test|h[Test]|h|r", - [](ChatHandler*, Hyperlink<player> player) + SECTION("Two strings") + { + TestChatCommand("two strings", [](ChatHandler*, std::string_view v1, std::string_view v2) + { + REQUIRE(v1 == "two"); + REQUIRE(v2 == "strings"); + return true; + }); + TestChatCommand("two strings", [](ChatHandler*, std::string_view) { return true; }, false); + TestChatCommand("two strings", [](ChatHandler*, Tail t) { - REQUIRE("Test"sv == *player); + REQUIRE(t == "two strings"); return true; - } - ); + }); + } } |