[3.3.5] Core/ChatCommands: Show error messages from argument parsers (PR #25443)

(cherry picked from commit 75f9e7396e35360f3016cc0cb21e72e20f5d96d5)
This commit is contained in:
Treeston
2022-02-05 23:42:15 +01:00
committed by Shauren
parent 7d0586812a
commit 378691aaa2
22 changed files with 381 additions and 206 deletions
@@ -0,0 +1,20 @@
--
DELETE FROM `trinity_string` WHERE `entry` BETWEEN 1500 AND 1516;
INSERT INTO `trinity_string` (`entry`,`content_default`) VALUES
(1500, 'Either:'),
(1501, 'Or: '),
(1502, 'Value \'%.*s\' is not valid for type %s.'),
(1503, 'Invalid UTF-8 sequences found in string.'),
(1504, 'Provided link has invalid link data.'),
(1505, 'Account \'%.*s\' does not exist.'),
(1506, 'Account ID %u does not exist.'),
(1507, '%s does not exist.'),
(1508, 'Character \'%.*s\' does not exist.'),
(1509, '\'%.*s\' is not a valid character name.'),
(1510, 'Achievement ID %u does not exist.'),
(1511, 'Teleport location %u does not exist.'),
(1512, 'Teleport location \'%.*s\' does not exist.'),
(1513, 'Item ID %u does not exist.'),
(1514, 'Spell ID %u does not exist.'),
(1515, 'Expected \'%.*s\', got \'%.*s\' instead.'),
(1516, 'Currency ID %u does not exist.');
+3
View File
@@ -144,6 +144,9 @@
#define SZFMTD "%" PRIuPTR #define SZFMTD "%" PRIuPTR
#define STRING_VIEW_FMT "%.*s"
#define STRING_VIEW_FMT_ARG(str) static_cast<int>((str).length()), (str).data()
typedef int64_t int64; typedef int64_t int64;
typedef int32_t int32; typedef int32_t int32;
typedef int16_t int16; typedef int16_t int16;
+14 -7
View File
@@ -21,6 +21,7 @@
#include "IpAddress.h" #include "IpAddress.h"
#include "StringConvert.h" #include "StringConvert.h"
#include "StringFormat.h" #include "StringFormat.h"
#include <boost/core/demangle.hpp>
#include <utf8.h> #include <utf8.h>
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
@@ -831,19 +832,25 @@ void Trinity::Impl::HexStrToByteArray(std::string_view str, uint8* out, size_t o
} }
} }
bool StringEqualI(std::string_view str1, std::string_view str2) bool StringEqualI(std::string_view a, std::string_view b)
{ {
return std::equal(str1.begin(), str1.end(), str2.begin(), str2.end(), return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return std::tolower(c1) == std::tolower(c2); });
[](char a, char b)
{
return std::tolower(a) == std::tolower(b);
});
} }
bool StringContainsStringI(std::string_view haystack, std::string_view needle) bool StringContainsStringI(std::string_view haystack, std::string_view needle)
{ {
return haystack.end() != return haystack.end() !=
std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), [](char c1, char c2) { return std::toupper(c1) == std::toupper(c2); }); std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), [](char c1, char c2) { return std::tolower(c1) == std::tolower(c2); });
}
bool StringCompareLessI(std::string_view a, std::string_view b)
{
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
}
std::string GetTypeName(std::type_info const& info)
{
return boost::core::demangle(info.name());
} }
float DegToRad(float degrees) float DegToRad(float degrees)
+13
View File
@@ -25,6 +25,7 @@
#include <array> #include <array>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <typeinfo>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -377,6 +378,12 @@ inline bool ValueContainsStringI(std::pair<T, std::string_view> const& haystack,
{ {
return StringContainsStringI(haystack.second, needle); return StringContainsStringI(haystack.second, needle);
} }
TC_COMMON_API bool StringCompareLessI(std::string_view a, std::string_view b);
struct StringCompareLessI_T
{
bool operator()(std::string_view a, std::string_view b) const { return StringCompareLessI(a, b); }
};
// simple class for not-modifyable list // simple class for not-modifyable list
template <typename T> template <typename T>
@@ -599,6 +606,12 @@ Ret* Coalesce(T1* first, T*... rest)
return static_cast<Ret*>(first); return static_cast<Ret*>(first);
} }
TC_COMMON_API std::string GetTypeName(std::type_info const&);
template <typename T>
std::string GetTypeName() { return GetTypeName(typeid(T)); }
template <typename T>
std::enable_if_t<!std::is_same_v<std::decay_t<T>, std::type_info>, std::string> GetTypeName(T&& v) { return GetTypeName(typeid(v)); }
template<typename T> template<typename T>
struct NonDefaultConstructible struct NonDefaultConstructible
{ {
+3 -3
View File
@@ -175,7 +175,7 @@ bool ChatHandler::hasStringAbbr(const char* name, const char* part)
return true; return true;
} }
void ChatHandler::SendSysMessage(const char *str, bool escapeCharacters) void ChatHandler::SendSysMessage(std::string_view str, bool escapeCharacters)
{ {
std::string msg{ str }; std::string msg{ str };
@@ -998,7 +998,7 @@ bool CliHandler::isAvailable(ChatCommand const& cmd) const
return cmd.AllowConsole; return cmd.AllowConsole;
} }
void CliHandler::SendSysMessage(const char *str, bool /*escapeCharacters*/) void CliHandler::SendSysMessage(std::string_view str, bool /*escapeCharacters*/)
{ {
m_print(m_callbackArg, str); m_print(m_callbackArg, str);
m_print(m_callbackArg, "\r\n"); m_print(m_callbackArg, "\r\n");
@@ -1155,7 +1155,7 @@ void AddonChannelCommandHandler::SendFailed() // f Command failed, no body
} }
// m Command message, message in body // m Command message, message in body
void AddonChannelCommandHandler::SendSysMessage(char const* str, bool escapeCharacters) void AddonChannelCommandHandler::SendSysMessage(std::string_view str, bool escapeCharacters)
{ {
ASSERT(echo); ASSERT(echo);
if (!hadAck) if (!hadAck)
+4 -4
View File
@@ -49,7 +49,7 @@ class TC_GAME_API ChatHandler
// function with different implementation for chat/console // function with different implementation for chat/console
virtual char const* GetTrinityString(uint32 entry) const; virtual char const* GetTrinityString(uint32 entry) const;
virtual void SendSysMessage(char const* str, bool escapeCharacters = false); virtual void SendSysMessage(std::string_view str, bool escapeCharacters = false);
void SendSysMessage(uint32 entry); void SendSysMessage(uint32 entry);
@@ -138,14 +138,14 @@ class TC_GAME_API ChatHandler
class TC_GAME_API CliHandler : public ChatHandler class TC_GAME_API CliHandler : public ChatHandler
{ {
public: public:
typedef void Print(void*, char const*); using Print = void(void*, std::string_view);
explicit CliHandler(void* callbackArg, Print* zprint) : m_callbackArg(callbackArg), m_print(zprint) { } explicit CliHandler(void* callbackArg, Print* zprint) : m_callbackArg(callbackArg), m_print(zprint) { }
// overwrite functions // overwrite functions
char const* GetTrinityString(uint32 entry) const override; char const* GetTrinityString(uint32 entry) const override;
bool isAvailable(ChatCommand const& cmd) const override; bool isAvailable(ChatCommand const& cmd) const override;
bool HasPermission(uint32 /*permission*/) const override { return true; } bool HasPermission(uint32 /*permission*/) const override { return true; }
void SendSysMessage(const char *str, bool escapeCharacters) override; void SendSysMessage(std::string_view, bool escapeCharacters) override;
bool ParseCommands(char const* str) override; bool ParseCommands(char const* str) override;
std::string GetNameLink() const override; std::string GetNameLink() const override;
bool needReportToTarget(Player* chr) const override; bool needReportToTarget(Player* chr) const override;
@@ -164,7 +164,7 @@ class TC_GAME_API AddonChannelCommandHandler : public ChatHandler
using ChatHandler::ChatHandler; using ChatHandler::ChatHandler;
bool ParseCommands(char const* str) override; bool ParseCommands(char const* str) override;
void SendSysMessage(char const* str, bool escapeCharacters) override; void SendSysMessage(std::string_view, bool escapeCharacters) override;
using ChatHandler::SendSysMessage; using ChatHandler::SendSysMessage;
bool IsHumanReadable() const override { return humanReadable; } bool IsHumanReadable() const override { return humanReadable; }
+35 -35
View File
@@ -25,77 +25,72 @@
#include "Errors.h" #include "Errors.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "Optional.h" #include "Optional.h"
#include "StringFormat.h"
#include <cstddef> #include <cstddef>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
class ChatHandler; class ChatHandler;
class CommandArgs;
namespace Trinity::Impl::ChatCommands namespace Trinity::Impl::ChatCommands
{ {
template <typename T>
struct SingleConsumer
{
static Optional<std::string_view> TryConsumeTo(T& val, std::string_view args)
{
return ArgInfo<T>::TryConsume(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*>
{
static Optional<std::string_view> TryConsumeTo(char const*& arg, std::string_view args) { arg = args.data(); return std::string_view(); }
};
// forward declaration // forward declaration
// ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer // ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer
// the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc // the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc
// MultiConsumer calls SingleConsumer in each iteration // MultiConsumer goes into ArgInfo for parsing on each iteration
template <typename Tuple, size_t offset> template <typename Tuple, size_t offset>
Optional<std::string_view> ConsumeFromOffset(Tuple&, std::string_view args); ChatCommandResult ConsumeFromOffset(Tuple&, ChatHandler const* handler, std::string_view args);
template <typename Tuple, typename NextType, size_t offset> template <typename Tuple, typename NextType, size_t offset>
struct MultiConsumer struct MultiConsumer
{ {
static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args) static ChatCommandResult TryConsumeTo(Tuple& tuple, ChatHandler const* handler, std::string_view args)
{ {
if (Optional<std::string_view> next = SingleConsumer<NextType>::TryConsumeTo(std::get<offset>(tuple), args)) ChatCommandResult next = ArgInfo<NextType>::TryConsume(std::get<offset>(tuple), handler, args);
return ConsumeFromOffset<Tuple, offset + 1>(tuple, *next); if (next)
return ConsumeFromOffset<Tuple, offset + 1>(tuple, handler, *next);
else else
return std::nullopt; return next;
} }
}; };
template <typename Tuple, typename NestedNextType, size_t offset> template <typename Tuple, typename NestedNextType, size_t offset>
struct MultiConsumer<Tuple, Optional<NestedNextType>, offset> struct MultiConsumer<Tuple, Optional<NestedNextType>, offset>
{ {
static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args) static ChatCommandResult TryConsumeTo(Tuple& tuple, ChatHandler const* handler, std::string_view args)
{ {
// try with the argument // try with the argument
auto& myArg = std::get<offset>(tuple); auto& myArg = std::get<offset>(tuple);
myArg.emplace(); myArg.emplace();
if (Optional<std::string_view> next = SingleConsumer<NestedNextType>::TryConsumeTo(myArg.value(), args))
if ((next = ConsumeFromOffset<Tuple, offset + 1>(tuple, *next))) ChatCommandResult result1 = ArgInfo<NestedNextType>::TryConsume(myArg.value(), handler, args);
return next; if (result1)
if ((result1 = ConsumeFromOffset<Tuple, offset + 1>(tuple, handler, *result1)))
return result1;
// try again omitting the argument // try again omitting the argument
myArg = std::nullopt; myArg = std::nullopt;
return ConsumeFromOffset<Tuple, offset + 1>(tuple, args); ChatCommandResult result2 = ConsumeFromOffset<Tuple, offset + 1>(tuple, handler, args);
if (result2)
return result2;
if (result1.HasErrorMessage() && result2.HasErrorMessage())
{
return Trinity::StringFormat("%s \"%s\"\n%s \"%s\"",
GetTrinityString(handler, LANG_CMDPARSER_EITHER), result2.GetErrorMessage().c_str(),
GetTrinityString(handler, LANG_CMDPARSER_OR), result1.GetErrorMessage().c_str());
}
else if (result1.HasErrorMessage())
return result1;
else
return result2;
} }
}; };
template <typename Tuple, size_t offset> template <typename Tuple, size_t offset>
Optional<std::string_view> ConsumeFromOffset(Tuple& tuple, std::string_view args) ChatCommandResult ConsumeFromOffset([[maybe_unused]] Tuple& tuple, [[maybe_unused]] ChatHandler const* handler, std::string_view args)
{ {
if constexpr (offset < std::tuple_size_v<Tuple>) if constexpr (offset < std::tuple_size_v<Tuple>)
return MultiConsumer<Tuple, std::tuple_element_t<offset, Tuple>, offset>::TryConsumeTo(tuple, args); return MultiConsumer<Tuple, std::tuple_element_t<offset, Tuple>, offset>::TryConsumeTo(tuple, handler, args);
else if (!args.empty()) /* the entire string must be consumed */ else if (!args.empty()) /* the entire string must be consumed */
return std::nullopt; return std::nullopt;
else else
@@ -123,10 +118,15 @@ class TC_GAME_API ChatCommand
Tuple arguments; Tuple arguments;
std::get<0>(arguments) = chatHandler; std::get<0>(arguments) = chatHandler;
if (Trinity::Impl::ChatCommands::ConsumeFromOffset<Tuple, 1>(arguments, argsStr)) Trinity::Impl::ChatCommands::ChatCommandResult result = Trinity::Impl::ChatCommands::ConsumeFromOffset<Tuple, 1>(arguments, chatHandler, argsStr);
if (result)
return std::apply(reinterpret_cast<TypedHandler>(handler), std::move(arguments)); return std::apply(reinterpret_cast<TypedHandler>(handler), std::move(arguments));
else else
{
if (result.HasErrorMessage())
Trinity::Impl::ChatCommands::SendErrorMessageToHandler(chatHandler, result.GetErrorMessage());
return false; return false;
}
}; };
_handler = reinterpret_cast<void*>(handler); _handler = reinterpret_cast<void*>(handler);
} }
@@ -23,6 +23,7 @@
#include "Util.h" #include "Util.h"
using namespace Trinity::ChatCommands; using namespace Trinity::ChatCommands;
using ChatCommandResult = Trinity::Impl::ChatCommands::ChatCommandResult;
struct AchievementVisitor struct AchievementVisitor
{ {
@@ -30,13 +31,14 @@ struct AchievementVisitor
value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; } value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; }
value_type operator()(uint32 achId) const { return sAchievementStore.LookupEntry(achId); } value_type operator()(uint32 achId) const { return sAchievementStore.LookupEntry(achId); }
}; };
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, std::string_view args) ChatCommandResult Trinity::Impl::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, ChatHandler const* handler, std::string_view args)
{ {
Variant<Hyperlink<achievement>, uint32> val; Variant<Hyperlink<achievement>, uint32> val;
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args);
if (next) if (!result || (data = val.visit(AchievementVisitor())))
if ((data = val.visit(AchievementVisitor()))) return result;
return next; if (uint32* id = std::get_if<uint32>(&val))
return FormatTrinityString(handler, LANG_CMDPARSER_ACHIEVEMENT_NO_EXIST, *id);
return std::nullopt; return std::nullopt;
} }
@@ -46,13 +48,14 @@ struct CurrencyTypesVisitor
value_type operator()(Hyperlink<currency> currency) const { return currency->Currency; } value_type operator()(Hyperlink<currency> currency) const { return currency->Currency; }
value_type operator()(uint32 currencyId) const { return sCurrencyTypesStore.LookupEntry(currencyId); } value_type operator()(uint32 currencyId) const { return sCurrencyTypesStore.LookupEntry(currencyId); }
}; };
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<CurrencyTypesEntry const*>::TryConsume(CurrencyTypesEntry const*& data, std::string_view args) ChatCommandResult Trinity::Impl::ChatCommands::ArgInfo<CurrencyTypesEntry const*>::TryConsume(CurrencyTypesEntry const*& data, ChatHandler const* handler, std::string_view args)
{ {
Variant<Hyperlink<currency>, uint32> val; Variant<Hyperlink<currency>, uint32> val;
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args);
if (next) if (!result || (data = val.visit(CurrencyTypesVisitor())))
if ((data = val.visit(CurrencyTypesVisitor()))) return result;
return next; if (uint32* id = std::get_if<uint32>(&val))
return FormatTrinityString(handler, LANG_CMDPARSER_CURRENCY_NO_EXIST, *id);
return std::nullopt; return std::nullopt;
} }
@@ -60,16 +63,18 @@ struct GameTeleVisitor
{ {
using value_type = GameTele const*; using value_type = GameTele const*;
value_type operator()(Hyperlink<tele> tele) const { return sObjectMgr->GetGameTele(tele); } value_type operator()(Hyperlink<tele> tele) const { return sObjectMgr->GetGameTele(tele); }
value_type operator()(std::string const& tele) const { return sObjectMgr->GetGameTele(tele); } value_type operator()(std::string_view tele) const { return sObjectMgr->GetGameTele(tele); }
}; };
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, std::string_view args) ChatCommandResult Trinity::Impl::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, ChatHandler const* handler, std::string_view args)
{ {
Variant<Hyperlink<tele>, std::string> val; Variant<Hyperlink<tele>, std::string_view> val;
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args);
if (next) if (!result || (data = val.visit(GameTeleVisitor())))
if ((data = val.visit(GameTeleVisitor()))) return result;
return next; if (val.holds_alternative<Hyperlink<tele>>())
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_GAME_TELE_ID_NO_EXIST, static_cast<uint32>(std::get<Hyperlink<tele>>(val)));
else
return FormatTrinityString(handler, LANG_CMDPARSER_GAME_TELE_NO_EXIST, STRING_VIEW_FMT_ARG(std::get<std::string_view>(val)));
} }
struct ItemTemplateVisitor struct ItemTemplateVisitor
@@ -78,13 +83,14 @@ struct ItemTemplateVisitor
value_type operator()(Hyperlink<item> item) const { return item->Item; } value_type operator()(Hyperlink<item> item) const { return item->Item; }
value_type operator()(uint32 item) const { return sObjectMgr->GetItemTemplate(item); } value_type operator()(uint32 item) const { return sObjectMgr->GetItemTemplate(item); }
}; };
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<ItemTemplate const*>::TryConsume(ItemTemplate const*& data, std::string_view args) ChatCommandResult Trinity::Impl::ChatCommands::ArgInfo<ItemTemplate const*>::TryConsume(ItemTemplate const*& data, ChatHandler const* handler, std::string_view args)
{ {
Variant<Hyperlink<item>, uint32> val; Variant<Hyperlink<item>, uint32> val;
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args);
if (next) if (!result || (data = val.visit(ItemTemplateVisitor())))
if ((data = val.visit(ItemTemplateVisitor()))) return result;
return next; if (uint32* id = std::get_if<uint32>(&val))
return FormatTrinityString(handler, LANG_CMDPARSER_ITEM_NO_EXIST, *id);
return std::nullopt; return std::nullopt;
} }
@@ -103,12 +109,13 @@ struct SpellInfoVisitor
value_type operator()(uint32 spellId) const { return sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); } value_type operator()(uint32 spellId) const { return sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); }
}; };
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, std::string_view args) ChatCommandResult Trinity::Impl::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, ChatHandler const* handler, std::string_view args)
{ {
Variant<Hyperlink<apower>, Hyperlink<conduit>, Hyperlink<enchant>, Hyperlink<mawpower>, Hyperlink<pvptal>, Hyperlink<spell>, Hyperlink<talent>, Hyperlink<trade>, uint32> val; Variant<Hyperlink<apower>, Hyperlink<conduit>, Hyperlink<enchant>, Hyperlink<mawpower>, Hyperlink<pvptal>, Hyperlink<spell>, Hyperlink<talent>, Hyperlink<trade>, uint32> val;
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args);
if (next) if (!result || (data = val.visit(SpellInfoVisitor())))
if ((data = val.visit(SpellInfoVisitor()))) return result;
return next; if (uint32* id = std::get_if<uint32>(&val))
return FormatTrinityString(handler, LANG_CMDPARSER_SPELL_NO_EXIST, *id);
return std::nullopt; return std::nullopt;
} }
@@ -22,6 +22,7 @@
#include "ChatCommandTags.h" #include "ChatCommandTags.h"
#include "SmartEnum.h" #include "SmartEnum.h"
#include "StringConvert.h" #include "StringConvert.h"
#include "StringFormat.h"
#include "Util.h" #include "Util.h"
#include <charconv> #include <charconv>
#include <map> #include <map>
@@ -36,10 +37,13 @@ namespace Trinity::Impl::ChatCommands
/************************** ARGUMENT HANDLERS *******************************************\ /************************** ARGUMENT HANDLERS *******************************************\
|* Define how to extract contents of a certain requested type from a string *| |* Define how to extract contents of a certain requested type from a string *|
|* Must implement the following: *| |* Must implement the following: *|
|* - TryConsume: T&, std::string_view -> Optional<std::string_view> *| |* - TryConsume: T&, ChatHandler const*, std::string_view -> ChatCommandResult *|
|* returns nullopt if no match, otherwise tail of argument string *| |* - on match, returns tail of the provided argument string (as std::string_view) *|
|* - if nullopt is returned, state of T& is indeterminate *| |* - on specific error, returns error message (as std::string&& or char const*) *|
|* - otherwise, T& should be initialized to the intended return value *| |* - on generic error, returns std::nullopt (this will print command usage) *|
|* *|
|* - if a match is returned, T& should be initialized to the matched value *|
|* - otherwise, the state of T& is indeterminate and caller will not use it *|
|* *| |* *|
\****************************************************************************************/ \****************************************************************************************/
template <typename T, typename = void> template <typename T, typename = void>
@@ -49,7 +53,7 @@ namespace Trinity::Impl::ChatCommands
template <typename T> template <typename T>
struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>> struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>>
{ {
static Optional<std::string_view> TryConsume(T& val, std::string_view args) static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args)
{ {
auto [token, tail] = tokenize(args); auto [token, tail] = tokenize(args);
if (token.empty()) if (token.empty())
@@ -58,23 +62,33 @@ namespace Trinity::Impl::ChatCommands
if (Optional<T> v = StringTo<T>(token, 0)) if (Optional<T> v = StringTo<T>(token, 0))
val = *v; val = *v;
else else
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), GetTypeName<T>().c_str());
if constexpr (std::is_floating_point_v<T>) if constexpr (std::is_floating_point_v<T>)
{ {
if (!std::isfinite(val)) if (!std::isfinite(val))
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), GetTypeName<T>().c_str());
} }
return tail; return tail;
} }
}; };
/*
for backwards compatibility, consumes the rest of the string
new code should use the Tail/WTail tags defined in ChatCommandTags
*/
template <>
struct ArgInfo<char const*, void>
{
static ChatCommandResult TryConsume(char const*& arg, ChatHandler const*, std::string_view args) { arg = args.data(); return std::string_view(); }
};
// string_view // string_view
template <> template <>
struct ArgInfo<std::string_view, void> struct ArgInfo<std::string_view, void>
{ {
static Optional<std::string_view> TryConsume(std::string_view& val, std::string_view args) static ChatCommandResult TryConsume(std::string_view& val, ChatHandler const*, std::string_view args)
{ {
auto [token, next] = tokenize(args); auto [token, next] = tokenize(args);
if (token.empty()) if (token.empty())
@@ -88,10 +102,10 @@ namespace Trinity::Impl::ChatCommands
template <> template <>
struct ArgInfo<std::string, void> struct ArgInfo<std::string, void>
{ {
static Optional<std::string_view> TryConsume(std::string& val, std::string_view args) static ChatCommandResult TryConsume(std::string& val, ChatHandler const* handler, std::string_view args)
{ {
std::string_view view; std::string_view view;
Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(view, args); ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(view, handler, args);
if (next) if (next)
val.assign(view); val.assign(view);
return next; return next;
@@ -102,13 +116,18 @@ namespace Trinity::Impl::ChatCommands
template <> template <>
struct ArgInfo<std::wstring, void> struct ArgInfo<std::wstring, void>
{ {
static Optional<std::string_view> TryConsume(std::wstring& val, std::string_view args) static ChatCommandResult TryConsume(std::wstring& val, ChatHandler const* handler, std::string_view args)
{ {
std::string_view utf8view; std::string_view utf8view;
Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(utf8view, args); ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(utf8view, handler, args);
if (next && Utf8toWStr(utf8view, val)) if (next)
return next; {
if (Utf8toWStr(utf8view, val))
return next;
else
return GetTrinityString(handler, LANG_CMDPARSER_INVALID_UTF8);
}
else else
return std::nullopt; return std::nullopt;
} }
@@ -118,19 +137,18 @@ namespace Trinity::Impl::ChatCommands
template <typename T> template <typename T>
struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>> struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>>
{ {
static std::map<std::string, Optional<T>> MakeSearchMap() using SearchMap = std::map<std::string_view, Optional<T>, StringCompareLessI_T>;
static SearchMap MakeSearchMap()
{ {
std::map<std::string, Optional<T>> map; SearchMap map;
for (T val : EnumUtils::Iterate<T>()) for (T val : EnumUtils::Iterate<T>())
{ {
EnumText text = EnumUtils::ToString(val); EnumText text = EnumUtils::ToString(val);
std::string title(text.Title); std::string_view title(text.Title);
strToLower(title); std::string_view constant(text.Constant);
std::string constant(text.Constant);
strToLower(constant);
auto [constantIt, constantNew] = map.try_emplace(constant, val); auto [constantIt, constantNew] = map.try_emplace(title, val);
if (!constantNew) if (!constantNew)
constantIt->second = std::nullopt; constantIt->second = std::nullopt;
@@ -144,21 +162,19 @@ namespace Trinity::Impl::ChatCommands
return map; return map;
} }
static inline std::map<std::string, Optional<T>> const SearchMap = MakeSearchMap(); static inline SearchMap const _map = MakeSearchMap();
static T const* Match(std::string s) static T const* Match(std::string_view s)
{ {
strToLower(s); auto it = _map.lower_bound(s);
if (it == _map.end() || !StringStartsWithI(it->first, s)) // not a match
auto it = SearchMap.lower_bound(s);
if (it == SearchMap.end() || !StringStartsWith(it->first, s)) // not a match
return nullptr; return nullptr;
if (it->first != s) // we don't have an exact match - check if it is unique if (!StringEqualI(it->first, s)) // we don't have an exact match - check if it is unique
{ {
auto it2 = it; auto it2 = it;
++it2; ++it2;
if (it2 != SearchMap.end() && StringStartsWith(it2->first, s)) // not unique if ((it2 != _map.end()) && StringStartsWithI(it2->first, s)) // not unique
return nullptr; return nullptr;
} }
@@ -168,31 +184,35 @@ namespace Trinity::Impl::ChatCommands
return nullptr; return nullptr;
} }
static Optional<std::string_view> TryConsume(T& val, std::string_view args) static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args)
{ {
std::string strVal; std::string_view strVal;
Optional<std::string_view> next = ArgInfo<std::string>::TryConsume(strVal, args); ChatCommandResult next1 = ArgInfo<std::string_view>::TryConsume(strVal, handler, args);
if (next1)
if (next)
{ {
if (T const* match = Match(strVal)) if (T const* match = Match(strVal))
{ {
val = *match; val = *match;
return next; return next1;
} }
} }
// Value not found. Try to parse arg as underlying type and cast it to enum type // Value not found. Try to parse arg as underlying type and cast it to enum type
using U = std::underlying_type_t<T>; using U = std::underlying_type_t<T>;
U uVal = 0; U uVal = 0;
next = ArgInfo<U>::TryConsume(uVal, args); if (ChatCommandResult next2 = ArgInfo<U>::TryConsume(uVal, handler, args))
if (next && EnumUtils::IsValid<T>(uVal))
{ {
val = static_cast<T>(uVal); if (EnumUtils::IsValid<T>(uVal))
return next; {
val = static_cast<T>(uVal);
return next2;
}
} }
return std::nullopt; if (next1)
return FormatTrinityString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(strVal), GetTypeName<T>().c_str());
else
return next1;
} }
}; };
@@ -200,9 +220,9 @@ namespace Trinity::Impl::ChatCommands
template <typename T> template <typename T>
struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>>
{ {
static Optional<std::string_view> TryConsume(T& tag, std::string_view args) static ChatCommandResult TryConsume(T& tag, ChatHandler const* handler, std::string_view args)
{ {
return tag.TryConsume(args); return tag.TryConsume(handler, args);
} }
}; };
@@ -210,16 +230,16 @@ namespace Trinity::Impl::ChatCommands
template <typename T> template <typename T>
struct ArgInfo<std::vector<T>, void> struct ArgInfo<std::vector<T>, void>
{ {
static Optional<std::string_view> TryConsume(std::vector<T>& val, std::string_view args) static ChatCommandResult TryConsume(std::vector<T>& val, ChatHandler const* handler, std::string_view args)
{ {
val.clear(); val.clear();
Optional<std::string_view> next = ArgInfo<T>::TryConsume(val.emplace_back(), args); ChatCommandResult next = ArgInfo<T>::TryConsume(val.emplace_back(), handler, args);
if (!next) if (!next)
return std::nullopt; return next;
while (Optional<std::string_view> next2 = ArgInfo<T>::TryConsume(val.emplace_back(), *next)) while (ChatCommandResult next2 = ArgInfo<T>::TryConsume(val.emplace_back(), handler, *next))
next = next2; next = std::move(next2);
val.pop_back(); val.pop_back();
return next; return next;
@@ -230,12 +250,12 @@ namespace Trinity::Impl::ChatCommands
template <typename T, size_t N> template <typename T, size_t N>
struct ArgInfo<std::array<T, N>, void> struct ArgInfo<std::array<T, N>, void>
{ {
static Optional<std::string_view> TryConsume(std::array<T, N>& val, std::string_view args) static ChatCommandResult TryConsume(std::array<T, N>& val, ChatHandler const* handler, std::string_view args)
{ {
Optional<std::string_view> next = args; ChatCommandResult next = args;
for (T& t : val) for (T& t : val)
if (!(next = ArgInfo<T>::TryConsume(t, *next))) if (!(next = ArgInfo<T>::TryConsume(t, handler, *next)))
return std::nullopt; break;
return next; return next;
} }
}; };
@@ -248,22 +268,36 @@ namespace Trinity::Impl::ChatCommands
static constexpr size_t N = std::variant_size_v<V>; static constexpr size_t N = std::variant_size_v<V>;
template <size_t I> template <size_t I>
static Optional<std::string_view> TryAtIndex(Trinity::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] std::string_view args) static ChatCommandResult TryAtIndex([[maybe_unused]] Trinity::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] ChatHandler const* handler, [[maybe_unused]] std::string_view args)
{ {
if constexpr (I < N) if constexpr (I < N)
{ {
if (Optional<std::string_view> next = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), args)) ChatCommandResult thisResult = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), handler, args);
return next; if (thisResult)
return thisResult;
else else
return TryAtIndex<I + 1>(val, args); {
ChatCommandResult nestedResult = TryAtIndex<I + 1>(val, handler, args);
if (!thisResult.HasErrorMessage())
return nestedResult;
if (!nestedResult.HasErrorMessage())
return thisResult;
if (StringStartsWith(nestedResult.GetErrorMessage(), "\""))
return Trinity::StringFormat("\"%s\"\n%s %s", thisResult.GetErrorMessage().c_str(), GetTrinityString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage().c_str());
else
return Trinity::StringFormat("\"%s\"\n%s \"%s\"", thisResult.GetErrorMessage().c_str(), GetTrinityString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage().c_str());
}
} }
else else
return std::nullopt; return std::nullopt;
} }
static Optional<std::string_view> TryConsume(Trinity::ChatCommands::Variant<Ts...>& val, std::string_view args) static ChatCommandResult TryConsume(Trinity::ChatCommands::Variant<Ts...>& val, ChatHandler const* handler, std::string_view args)
{ {
return TryAtIndex<0>(val, args); ChatCommandResult result = TryAtIndex<0>(val, handler, args);
if (result.HasErrorMessage() && (result.GetErrorMessage().find('\n') != std::string::npos))
return Trinity::StringFormat("%s %s", GetTrinityString(handler, LANG_CMDPARSER_EITHER), result.GetErrorMessage().c_str());
return result;
} }
}; };
@@ -271,35 +305,35 @@ namespace Trinity::Impl::ChatCommands
template <> template <>
struct TC_GAME_API ArgInfo<AchievementEntry const*> struct TC_GAME_API ArgInfo<AchievementEntry const*>
{ {
static Optional<std::string_view> TryConsume(AchievementEntry const*&, std::string_view); static ChatCommandResult TryConsume(AchievementEntry const*&, ChatHandler const*, std::string_view);
}; };
// CurrencyTypesEntry* from numeric id or link // CurrencyTypesEntry* from numeric id or link
template <> template <>
struct TC_GAME_API ArgInfo<CurrencyTypesEntry const*> struct TC_GAME_API ArgInfo<CurrencyTypesEntry const*>
{ {
static Optional<std::string_view> TryConsume(CurrencyTypesEntry const*&, std::string_view); static ChatCommandResult TryConsume(CurrencyTypesEntry const*&, ChatHandler const*, std::string_view);
}; };
// GameTele* from string name or link // GameTele* from string name or link
template <> template <>
struct TC_GAME_API ArgInfo<GameTele const*> struct TC_GAME_API ArgInfo<GameTele const*>
{ {
static Optional<std::string_view> TryConsume(GameTele const*&, std::string_view); static ChatCommandResult TryConsume(GameTele const*&, ChatHandler const*, std::string_view);
}; };
// ItemTemplate* from numeric id or link // ItemTemplate* from numeric id or link
template <> template <>
struct TC_GAME_API ArgInfo<ItemTemplate const*> struct TC_GAME_API ArgInfo<ItemTemplate const*>
{ {
static Optional<std::string_view> TryConsume(ItemTemplate const*&, std::string_view); static ChatCommandResult TryConsume(ItemTemplate const*&, ChatHandler const*, std::string_view);
}; };
// SpellInfo const* from spell id or link // SpellInfo const* from spell id or link
template <> template <>
struct TC_GAME_API ArgInfo<SpellInfo const*> struct TC_GAME_API ArgInfo<SpellInfo const*>
{ {
static Optional<std::string_view> TryConsume(SpellInfo const*&, std::string_view); static ChatCommandResult TryConsume(SpellInfo const*&, ChatHandler const*, std::string_view);
}; };
} }
@@ -0,0 +1,30 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ChatCommandHelpers.h"
#include "Chat.h"
void Trinity::Impl::ChatCommands::SendErrorMessageToHandler(ChatHandler* handler, std::string_view str)
{
handler->SendSysMessage(str);
handler->SetSentErrorMessage(true);
}
char const* Trinity::Impl::ChatCommands::GetTrinityString(ChatHandler const* handler, TrinityStrings which)
{
return handler->GetTrinityString(which);
}
@@ -18,8 +18,16 @@
#ifndef TRINITY_CHATCOMMANDHELPERS_H #ifndef TRINITY_CHATCOMMANDHELPERS_H
#define TRINITY_CHATCOMMANDHELPERS_H #define TRINITY_CHATCOMMANDHELPERS_H
#include "Define.h"
#include "Language.h"
#include "Optional.h"
#include "StringFormat.h"
#include <string>
#include <string_view> #include <string_view>
#include <type_traits> #include <type_traits>
#include <variant>
class ChatHandler;
namespace Trinity::Impl::ChatCommands namespace Trinity::Impl::ChatCommands
{ {
@@ -82,6 +90,43 @@ namespace Trinity::Impl::ChatCommands
template <std::size_t index, typename... Ts> template <std::size_t index, typename... Ts>
using get_nth_t = typename get_nth<index, Ts...>::type; using get_nth_t = typename get_nth<index, Ts...>::type;
// this essentially models std::optional<std::string_view>, except it can also hold an error message
// it has std::string_view's bool conversion and dereference operators
//
// monostate <-> unspecified error, typically end-of-string reached or parsing failed
// std::string <-> specified error, typically character-not-found or invalid item link
// std::string_view <-> success, string_view is remaining argument string
struct ChatCommandResult
{
ChatCommandResult(std::nullopt_t) : _storage() {}
ChatCommandResult(std::string const&) = delete;
ChatCommandResult(std::string&& s) : _storage(std::in_place_type<std::string>, std::forward<std::string>(s)) {}
ChatCommandResult(char const* c) : _storage(std::in_place_type<std::string>, c) {}
ChatCommandResult(std::string_view s) : _storage(std::in_place_type<std::string_view>, s) {}
ChatCommandResult(ChatCommandResult const&) = delete;
ChatCommandResult(ChatCommandResult&&) = default;
ChatCommandResult& operator=(ChatCommandResult const&) = delete;
ChatCommandResult& operator=(ChatCommandResult&&) = default;
std::string_view operator*() const { return std::get<std::string_view>(_storage); }
bool IsSuccessful() const { return std::holds_alternative<std::string_view>(_storage); }
explicit operator bool() const { return IsSuccessful(); }
bool HasErrorMessage() const { return std::holds_alternative<std::string>(_storage); }
std::string const& GetErrorMessage() const { return std::get<std::string>(_storage); }
private:
std::variant<std::monostate, std::string_view, std::string> _storage;
};
TC_GAME_API void SendErrorMessageToHandler(ChatHandler* handler, std::string_view str);
TC_GAME_API char const* GetTrinityString(ChatHandler const* handler, TrinityStrings which);
template <typename... Ts>
std::string FormatTrinityString(ChatHandler const* handler, TrinityStrings which, Ts&&... args)
{
return Trinity::StringFormat(GetTrinityString(handler, which), std::forward<Ts>(args)...);
}
} }
#endif #endif
@@ -25,19 +25,21 @@
#include "ObjectMgr.h" #include "ObjectMgr.h"
#include "Player.h" #include "Player.h"
Optional<std::string_view> Trinity::ChatCommands::QuotedString::TryConsume(std::string_view args) using namespace Trinity::Impl::ChatCommands;
ChatCommandResult Trinity::ChatCommands::QuotedString::TryConsume(ChatHandler const* handler, std::string_view args)
{ {
if (args.empty()) if (args.empty())
return std::nullopt; return std::nullopt;
if ((args[0] != '"') && (args[0] != '\'')) if ((args[0] != '"') && (args[0] != '\''))
return Trinity::Impl::ChatCommands::ArgInfo<std::string>::TryConsume(*this, args); return ArgInfo<std::string>::TryConsume(*this, handler, args);
char const QUOTE = args[0]; char const QUOTE = args[0];
for (size_t i = 1; i < args.length(); ++i) for (size_t i = 1; i < args.length(); ++i)
{ {
if (args[i] == QUOTE) if (args[i] == QUOTE)
{ {
auto [remainingToken, tail] = Trinity::Impl::ChatCommands::tokenize(args.substr(i + 1)); auto [remainingToken, tail] = tokenize(args.substr(i + 1));
if (remainingToken.empty()) // if this is not empty, then we did not consume the full token if (remainingToken.empty()) // if this is not empty, then we did not consume the full token
return tail; return tail;
else else
@@ -56,17 +58,17 @@ Optional<std::string_view> Trinity::ChatCommands::QuotedString::TryConsume(std::
return std::nullopt; return std::nullopt;
} }
Optional<std::string_view> Trinity::ChatCommands::AccountIdentifier::TryConsume(std::string_view args) ChatCommandResult Trinity::ChatCommands::AccountIdentifier::TryConsume(ChatHandler const* handler, std::string_view args)
{ {
std::string_view text; std::string_view text;
Optional<std::string_view> next = Trinity::Impl::ChatCommands::ArgInfo<std::string_view>::TryConsume(text, args); ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(text, handler, args);
if (!next) if (!next)
return std::nullopt; return next;
// first try parsing as account name // first try parsing as account name
_name.assign(text); _name.assign(text);
if (!Utf8ToUpperOnlyLatin(_name)) if (!Utf8ToUpperOnlyLatin(_name))
return std::nullopt; return GetTrinityString(handler, LANG_CMDPARSER_INVALID_UTF8);
_id = AccountMgr::GetId(_name); _id = AccountMgr::GetId(_name);
if (_id) // account with name exists, we are done if (_id) // account with name exists, we are done
return next; return next;
@@ -74,21 +76,21 @@ Optional<std::string_view> Trinity::ChatCommands::AccountIdentifier::TryConsume(
// try parsing as account id instead // try parsing as account id instead
Optional<uint32> id = Trinity::StringTo<uint32>(text, 10); Optional<uint32> id = Trinity::StringTo<uint32>(text, 10);
if (!id) if (!id)
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_ACCOUNT_NAME_NO_EXIST, STRING_VIEW_FMT_ARG(_name));
_id = *id; _id = *id;
if (AccountMgr::GetName(_id, _name)) if (AccountMgr::GetName(_id, _name))
return next; return next;
else else
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_ACCOUNT_ID_NO_EXIST, _id);
} }
Optional<std::string_view> Trinity::ChatCommands::PlayerIdentifier::TryConsume(std::string_view args) ChatCommandResult Trinity::ChatCommands::PlayerIdentifier::TryConsume(ChatHandler const* handler, std::string_view args)
{ {
Variant<Hyperlink<player>, ObjectGuid::LowType, std::string_view> val; Variant<Hyperlink<player>, ObjectGuid::LowType, std::string_view> val;
Optional<std::string_view> next = Trinity::Impl::ChatCommands::ArgInfo<decltype(val)>::TryConsume(val, args); ChatCommandResult next = ArgInfo<decltype(val)>::TryConsume(val, handler, args);
if (!next) if (!next)
return std::nullopt; return next;
if (val.holds_alternative<ObjectGuid::LowType>()) if (val.holds_alternative<ObjectGuid::LowType>())
{ {
@@ -96,7 +98,7 @@ Optional<std::string_view> Trinity::ChatCommands::PlayerIdentifier::TryConsume(s
if ((_player = ObjectAccessor::FindPlayerByLowGUID(_guid.GetCounter()))) if ((_player = ObjectAccessor::FindPlayerByLowGUID(_guid.GetCounter())))
_name = _player->GetName(); _name = _player->GetName();
else if (!sCharacterCache->GetCharacterNameByGuid(_guid, _name)) else if (!sCharacterCache->GetCharacterNameByGuid(_guid, _name))
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_CHAR_GUID_NO_EXIST, _guid.ToString().c_str());
return next; return next;
} }
else else
@@ -107,12 +109,12 @@ Optional<std::string_view> Trinity::ChatCommands::PlayerIdentifier::TryConsume(s
_name.assign(val.get<std::string_view>()); _name.assign(val.get<std::string_view>());
if (!normalizePlayerName(_name)) if (!normalizePlayerName(_name))
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_CHAR_NAME_INVALID, STRING_VIEW_FMT_ARG(_name));
if ((_player = ObjectAccessor::FindPlayerByName(_name))) if ((_player = ObjectAccessor::FindPlayerByName(_name)))
_guid = _player->GetGUID(); _guid = _player->GetGUID();
else if (!(_guid = sCharacterCache->GetCharacterGuidByName(_name))) else if (!(_guid = sCharacterCache->GetCharacterGuidByName(_name)))
return std::nullopt; return FormatTrinityString(handler, LANG_CMDPARSER_CHAR_NAME_NO_EXIST, STRING_VIEW_FMT_ARG(_name));
return next; return next;
} }
} }
@@ -41,7 +41,11 @@ class Player;
namespace Trinity::Impl::ChatCommands namespace Trinity::Impl::ChatCommands
{ {
struct ContainerTag {}; struct ContainerTag
{
using ChatCommandResult = Trinity::Impl::ChatCommands::ChatCommandResult;
};
template <typename T> template <typename T>
struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>>
{ {
@@ -71,8 +75,11 @@ namespace Trinity::ChatCommands
|* Simple holder classes to differentiate between extraction methods *| |* Simple holder classes to differentiate between extraction methods *|
|* Must inherit from Trinity::Impl::ChatCommands::ContainerTag *| |* Must inherit from Trinity::Impl::ChatCommands::ContainerTag *|
|* Must implement the following: *| |* Must implement the following: *|
|* - TryConsume: std::string_view -> Optional<std::string_view> *| |* - TryConsume: ChatHandler const*, std::string_view -> ChatCommandResult *|
|* returns nullopt if no match, otherwise the tail of the provided argument string *| |* - on match, returns tail of the provided argument string (as std::string_view) *|
|* - on specific error, returns error message (as std::string&& or char const*) *|
|* - on generic error, returns std::nullopt (this will print command usage) *|
|* *|
|* - typedef value_type of type that is contained within the tag *| |* - typedef value_type of type that is contained within the tag *|
|* - cast operator to value_type *| |* - cast operator to value_type *|
|* *| |* *|
@@ -83,15 +90,17 @@ namespace Trinity::ChatCommands
{ {
using value_type = void; using value_type = void;
Optional<std::string_view> TryConsume(std::string_view args) const ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args) const
{ {
if (StringStartsWithI(args, _string)) std::string_view start = args.substr(0, _string.length());
if (StringEqualI(start, _string))
{ {
auto [remainingToken, tail] = Trinity::Impl::ChatCommands::tokenize(args.substr(_string.length())); auto [remainingToken, tail] = Trinity::Impl::ChatCommands::tokenize(args.substr(_string.length()));
if (remainingToken.empty()) // if this is not empty, then we did not consume the full token if (remainingToken.empty()) // if this is not empty, then we did not consume the full token
return tail; return tail;
start = args.substr(0, _string.length() + remainingToken.length());
} }
return std::nullopt; return Trinity::Impl::ChatCommands::FormatTrinityString(handler, LANG_CMDPARSER_EXACT_SEQ_MISMATCH, STRING_VIEW_FMT_ARG(_string), STRING_VIEW_FMT_ARG(start));
} }
private: private:
@@ -108,7 +117,7 @@ namespace Trinity::ChatCommands
using std::string_view::operator=; using std::string_view::operator=;
Optional<std::string_view> TryConsume(std::string_view args) ChatCommandResult TryConsume(ChatHandler const*,std::string_view args)
{ {
std::string_view::operator=(args); std::string_view::operator=(args);
return std::string_view(); return std::string_view();
@@ -121,12 +130,12 @@ namespace Trinity::ChatCommands
using std::wstring::operator=; using std::wstring::operator=;
Optional<std::string_view> TryConsume(std::string_view args) ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args)
{ {
if (Utf8toWStr(args, *this)) if (Utf8toWStr(args, *this))
return std::string_view(); return std::string_view();
else else
return std::nullopt; return Trinity::Impl::ChatCommands::GetTrinityString(handler, LANG_CMDPARSER_INVALID_UTF8);
} }
}; };
@@ -134,7 +143,7 @@ namespace Trinity::ChatCommands
{ {
using value_type = std::string; using value_type = std::string;
TC_GAME_API Optional<std::string_view> TryConsume(std::string_view args); TC_GAME_API ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args);
}; };
struct TC_GAME_API AccountIdentifier : Trinity::Impl::ChatCommands::ContainerTag struct TC_GAME_API AccountIdentifier : Trinity::Impl::ChatCommands::ContainerTag
@@ -148,7 +157,7 @@ namespace Trinity::ChatCommands
uint32 GetID() const { return _id; } uint32 GetID() const { return _id; }
std::string const& GetName() const { return _name; } std::string const& GetName() const { return _name; }
Optional<std::string_view> TryConsume(std::string_view args); ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args);
private: private:
uint32 _id; uint32 _id;
@@ -171,7 +180,7 @@ namespace Trinity::ChatCommands
bool IsConnected() const { return (_player != nullptr); } bool IsConnected() const { return (_player != nullptr); }
Player* GetConnectedPlayer() const { return _player; } Player* GetConnectedPlayer() const { return _player; }
Optional<std::string_view> TryConsume(std::string_view args); ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args);
static Optional<PlayerIdentifier> FromTarget(ChatHandler* handler); static Optional<PlayerIdentifier> FromTarget(ChatHandler* handler);
static Optional<PlayerIdentifier> FromSelf(ChatHandler* handler); static Optional<PlayerIdentifier> FromSelf(ChatHandler* handler);
@@ -199,7 +208,7 @@ namespace Trinity::ChatCommands
value_type operator*() const { return val; } value_type operator*() const { return val; }
storage_type const* operator->() const { return &val; } storage_type const* operator->() const { return &val; }
Optional<std::string_view> TryConsume(std::string_view args) ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args)
{ {
Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseSingleHyperlink(args); Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseSingleHyperlink(args);
// invalid hyperlinks cannot be consumed // invalid hyperlinks cannot be consumed
@@ -212,7 +221,7 @@ namespace Trinity::ChatCommands
// store value // store value
if (!linktag::StoreTo(val, info.data)) if (!linktag::StoreTo(val, info.data))
return std::nullopt; return Trinity::Impl::ChatCommands::GetTrinityString(handler, LANG_CMDPARSER_LINKDATA_INVALID);
// finally, skip any potential delimiters // finally, skip any potential delimiters
auto [token, next] = Trinity::Impl::ChatCommands::tokenize(info.tail); auto [token, next] = Trinity::Impl::ChatCommands::tokenize(info.tail);
+17 -16
View File
@@ -994,22 +994,23 @@ enum TrinityStrings
// 1219-1499 - free // 1219-1499 - free
// Command argument parsers // Command argument parsers
LANG_CMDPARSER_EITHER = 1500, // 3.3.5 RESERVED LANG_CMDPARSER_EITHER = 1500,
LANG_CMDPARSER_OR = 1501, // 3.3.5 RESERVED LANG_CMDPARSER_OR = 1501,
LANG_CMDPARSER_STRING_VALUE_INVALID = 1502, // 3.3.5 RESERVED LANG_CMDPARSER_STRING_VALUE_INVALID = 1502,
LANG_CMDPARSER_INVALID_UTF8 = 1503, // 3.3.5 RESERVED LANG_CMDPARSER_INVALID_UTF8 = 1503,
LANG_CMDPARSER_LINKDATA_INVALID = 1504, // 3.3.5 RESERVED LANG_CMDPARSER_LINKDATA_INVALID = 1504,
LANG_CMDPARSER_ACCOUNT_NAME_NO_EXIST = 1505, // 3.3.5 RESERVED LANG_CMDPARSER_ACCOUNT_NAME_NO_EXIST = 1505,
LANG_CMDPARSER_ACCOUNT_ID_NO_EXIST = 1506, // 3.3.5 RESERVED LANG_CMDPARSER_ACCOUNT_ID_NO_EXIST = 1506,
LANG_CMDPARSER_CHAR_GUID_NO_EXIST = 1507, // 3.3.5 RESERVED LANG_CMDPARSER_CHAR_GUID_NO_EXIST = 1507,
LANG_CMDPARSER_CHAR_NAME_NO_EXIST = 1508, // 3.3.5 RESERVED LANG_CMDPARSER_CHAR_NAME_NO_EXIST = 1508,
LANG_CMDPARSER_CHAR_NAME_INVALID = 1509, // 3.3.5 RESERVED LANG_CMDPARSER_CHAR_NAME_INVALID = 1509,
LANG_CMDPARSER_ACHIEVEMENT_NO_EXIST = 1510, // 3.3.5 RESERVED LANG_CMDPARSER_ACHIEVEMENT_NO_EXIST = 1510,
LANG_CMDPARSER_GAME_TELE_ID_NO_EXIST = 1511, // 3.3.5 RESERVED LANG_CMDPARSER_GAME_TELE_ID_NO_EXIST = 1511,
LANG_CMDPARSER_GAME_TELE_NO_EXIST = 1512, // 3.3.5 RESERVED LANG_CMDPARSER_GAME_TELE_NO_EXIST = 1512,
LANG_CMDPARSER_ITEM_NO_EXIST = 1513, // 3.3.5 RESERVED LANG_CMDPARSER_ITEM_NO_EXIST = 1513,
LANG_CMDPARSER_SPELL_NO_EXIST = 1514, // 3.3.5 RESERVED LANG_CMDPARSER_SPELL_NO_EXIST = 1514,
LANG_CMDPARSER_EXACT_SEQ_MISMATCH = 1515, // 3.3.5 RESERVED LANG_CMDPARSER_EXACT_SEQ_MISMATCH = 1515,
LANG_CMDPARSER_CURRENCY_NO_EXIST = 1516,
// 1516-1998 - free // 1516-1998 - free
LANG_DEBUG_AREATRIGGER_LEFT = 1999, LANG_DEBUG_AREATRIGGER_LEFT = 1999,
+2 -2
View File
@@ -562,8 +562,8 @@ enum RealmZone
/// Storage class for commands issued for delayed execution /// Storage class for commands issued for delayed execution
struct TC_GAME_API CliCommandHolder struct TC_GAME_API CliCommandHolder
{ {
typedef void(*Print)(void*, char const*); using Print = void(*)(void*, std::string_view);
typedef void(*CommandFinished)(void*, bool success); using CommandFinished = void(*)(void*, bool success);
void* m_callbackArg; void* m_callbackArg;
char* m_command; char* m_command;
+1 -3
View File
@@ -41,9 +41,7 @@ EndScriptData */
#include "PoolMgr.h" #include "PoolMgr.h"
#include "RBAC.h" #include "RBAC.h"
#include "WorldSession.h" #include "WorldSession.h"
#include <boost/core/demangle.hpp>
#include <sstream> #include <sstream>
#include <typeinfo>
using namespace Trinity::ChatCommands; using namespace Trinity::ChatCommands;
@@ -574,7 +572,7 @@ public:
handler->PSendSysMessage(LANG_GOINFO_SIZE, gameObjectInfo->size); handler->PSendSysMessage(LANG_GOINFO_SIZE, gameObjectInfo->size);
handler->PSendSysMessage(LANG_OBJECTINFO_AIINFO, gameObjectInfo->AIName.c_str(), sObjectMgr->GetScriptName(gameObjectInfo->ScriptId).c_str()); handler->PSendSysMessage(LANG_OBJECTINFO_AIINFO, gameObjectInfo->AIName.c_str(), sObjectMgr->GetScriptName(gameObjectInfo->ScriptId).c_str());
if (GameObjectAI const* ai = thisGO ? thisGO->AI() : nullptr) if (GameObjectAI const* ai = thisGO ? thisGO->AI() : nullptr)
handler->PSendSysMessage(LANG_OBJECTINFO_AITYPE, boost::core::demangle(typeid(*ai).name()).c_str()); handler->PSendSysMessage(LANG_OBJECTINFO_AITYPE, GetTypeName(*ai).c_str());
if (GameObjectDisplayInfoEntry const* modelInfo = sGameObjectDisplayInfoStore.LookupEntry(displayId)) if (GameObjectDisplayInfoEntry const* modelInfo = sGameObjectDisplayInfoStore.LookupEntry(displayId))
handler->PSendSysMessage(LANG_GOINFO_MODEL, modelInfo->GeoBoxMax.X, modelInfo->GeoBoxMax.Y, modelInfo->GeoBoxMax.Z, modelInfo->GeoBoxMin.X, modelInfo->GeoBoxMin.Y, modelInfo->GeoBoxMin.Z); handler->PSendSysMessage(LANG_GOINFO_MODEL, modelInfo->GeoBoxMax.X, modelInfo->GeoBoxMax.Y, modelInfo->GeoBoxMax.Z, modelInfo->GeoBoxMin.X, modelInfo->GeoBoxMin.Y, modelInfo->GeoBoxMin.Z);
+1 -3
View File
@@ -45,8 +45,6 @@ EndScriptData */
#include "Transport.h" #include "Transport.h"
#include "World.h" #include "World.h"
#include "WorldSession.h" #include "WorldSession.h"
#include <boost/core/demangle.hpp>
#include <typeinfo>
using namespace Trinity::ChatCommands; using namespace Trinity::ChatCommands;
using CreatureSpawnId = Variant<Hyperlink<creature>, ObjectGuid::LowType>; using CreatureSpawnId = Variant<Hyperlink<creature>, ObjectGuid::LowType>;
@@ -550,7 +548,7 @@ public:
handler->PSendSysMessage(LANG_OBJECTINFO_AIINFO, target->GetAIName().c_str(), target->GetScriptName().c_str()); handler->PSendSysMessage(LANG_OBJECTINFO_AIINFO, target->GetAIName().c_str(), target->GetScriptName().c_str());
handler->PSendSysMessage(LANG_NPCINFO_REACTSTATE, DescribeReactState(target->GetReactState())); handler->PSendSysMessage(LANG_NPCINFO_REACTSTATE, DescribeReactState(target->GetReactState()));
if (CreatureAI const* ai = target->AI()) if (CreatureAI const* ai = target->AI())
handler->PSendSysMessage(LANG_OBJECTINFO_AITYPE, boost::core::demangle(typeid(*ai).name()).c_str()); handler->PSendSysMessage(LANG_OBJECTINFO_AITYPE, GetTypeName(*ai).c_str());
handler->PSendSysMessage(LANG_NPCINFO_FLAGS_EXTRA, cInfo->flags_extra); handler->PSendSysMessage(LANG_NPCINFO_FLAGS_EXTRA, cInfo->flags_extra);
for (CreatureFlagsExtra flag : EnumUtils::Iterate<CreatureFlagsExtra>()) for (CreatureFlagsExtra flag : EnumUtils::Iterate<CreatureFlagsExtra>())
if (cInfo->flags_extra & flag) if (cInfo->flags_extra & flag)
@@ -97,18 +97,17 @@ int cli_hook_func()
#endif #endif
void utf8print(void* /*arg*/, char const* str) void utf8print(void* /*arg*/, std::string_view str)
{ {
#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS #if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
wchar_t wtemp_buf[6000]; std::wstring wbuf;
size_t wtemp_len = 6000-1; if (!Utf8toWStr(str, wbuf))
if (!Utf8toWStr(str, strlen(str), wtemp_buf, wtemp_len))
return; return;
wprintf(L"%s", wtemp_buf); wprintf(L"%s", wbuf.c_str());
#else #else
{ {
printf("%s", str); printf(STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(str));
fflush(stdout); fflush(stdout);
} }
#endif #endif
@@ -91,7 +91,7 @@ void RASession::Start()
_socket.close(); _socket.close();
} }
int RASession::Send(char const* data) int RASession::Send(std::string_view data)
{ {
std::ostream os(&_writeBuffer); std::ostream os(&_writeBuffer);
os << data; os << data;
@@ -204,9 +204,9 @@ bool RASession::ProcessCommand(std::string& command)
return false; return false;
} }
void RASession::CommandPrint(void* callbackArg, char const* text) void RASession::CommandPrint(void* callbackArg, std::string_view text)
{ {
if (!text || !*text) if (text.empty())
return; return;
RASession* session = static_cast<RASession*>(callbackArg); RASession* session = static_cast<RASession*>(callbackArg);
@@ -42,13 +42,13 @@ public:
unsigned short GetRemotePort() const { return _socket.remote_endpoint().port(); } unsigned short GetRemotePort() const { return _socket.remote_endpoint().port(); }
private: private:
int Send(char const* data); int Send(std::string_view data);
std::string ReadString(); std::string ReadString();
bool CheckAccessLevel(const std::string& user); bool CheckAccessLevel(const std::string& user);
bool CheckPassword(const std::string& user, const std::string& pass); bool CheckPassword(const std::string& user, const std::string& pass);
bool ProcessCommand(std::string& command); bool ProcessCommand(std::string& command);
static void CommandPrint(void* callbackArg, char const* text); static void CommandPrint(void* callbackArg, std::string_view text);
static void CommandFinished(void* callbackArg, bool); static void CommandFinished(void* callbackArg, bool);
tcp::socket _socket; tcp::socket _socket;
+2 -2
View File
@@ -38,7 +38,7 @@ class SOAPCommand
{ {
} }
void appendToPrintBuffer(char const* msg) void appendToPrintBuffer(std::string_view msg)
{ {
m_printBuffer += msg; m_printBuffer += msg;
} }
@@ -54,7 +54,7 @@ class SOAPCommand
return m_success; return m_success;
} }
static void print(void* callbackArg, char const* msg) static void print(void* callbackArg, std::string_view msg)
{ {
((SOAPCommand*)callbackArg)->appendToPrintBuffer(msg); ((SOAPCommand*)callbackArg)->appendToPrintBuffer(msg);
} }
+10 -1
View File
@@ -17,15 +17,24 @@
#include "tc_catch2.h" #include "tc_catch2.h"
#include "Chat.h"
#include "ChatCommand.h" #include "ChatCommand.h"
using namespace Trinity::ChatCommands; using namespace Trinity::ChatCommands;
using namespace std::string_view_literals; using namespace std::string_view_literals;
struct DummyChatHandler : ChatHandler
{
DummyChatHandler() : ChatHandler(nullptr) {}
void SendSysMessage(std::string_view, bool) override {}
char const* GetTrinityString(uint32) const override { return ""; }
};
template <typename F> template <typename F>
static void TestChatCommand(char const* c, F f, Optional<bool> expected = true) static void TestChatCommand(char const* c, F f, Optional<bool> expected = true)
{ {
bool r = ChatCommand("", 0, false, +f, "")(nullptr, c); DummyChatHandler handler;
bool r = ChatCommand("", 0, false, +f, "")(&handler, c);
if (expected) if (expected)
REQUIRE(r == *expected); REQUIRE(r == *expected);
} }