TrinityCore
ChatCommandArgs.h
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#ifndef TRINITY_CHATCOMMANDARGS_H
19#define TRINITY_CHATCOMMANDARGS_H
20
21#include "ChatCommandHelpers.h"
22#include "ChatCommandTags.h"
23#include "SmartEnum.h"
24#include "StringConvert.h"
25#include "StringFormat.h"
26#include "Util.h"
27#include <charconv>
28#include <map>
29#include <string>
30#include <string_view>
31
32struct GameTele;
33
35{
36
37 /************************** ARGUMENT HANDLERS *******************************************\
38 |* Define how to extract contents of a certain requested type from a string *|
39 |* Must implement the following: *|
40 |* - TryConsume: T&, ChatHandler const*, std::string_view -> ChatCommandResult *|
41 |* - on match, returns tail of the provided argument string (as std::string_view) *|
42 |* - on specific error, returns error message (as std::string&& or char const*) *|
43 |* - on generic error, returns std::nullopt (this will print command usage) *|
44 |* *|
45 |* - if a match is returned, T& should be initialized to the matched value *|
46 |* - otherwise, the state of T& is indeterminate and caller will not use it *|
47 |* *|
48 \****************************************************************************************/
49 template <typename T, typename = void>
50 struct ArgInfo { static_assert(Trinity::dependant_false_v<T>, "Invalid command parameter type - see ChatCommandArgs.h for possible types"); };
51
52 // catch-all for number types
53 template <typename T>
54 struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>>
55 {
56 static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args)
57 {
58 auto [token, tail] = tokenize(args);
59 if (token.empty())
60 return std::nullopt;
61
62 if (Optional<T> v = StringTo<T>(token, 0))
63 val = *v;
64 else
65 return FormatTrinityString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), Trinity::GetTypeName<T>().c_str());
66
67 if constexpr (std::is_floating_point_v<T>)
68 {
69 if (!std::isfinite(val))
70 return FormatTrinityString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), Trinity::GetTypeName<T>().c_str());
71 }
72
73 return tail;
74 }
75 };
76
77 // string_view
78 template <>
79 struct ArgInfo<std::string_view, void>
80 {
81 static ChatCommandResult TryConsume(std::string_view& val, ChatHandler const*, std::string_view args)
82 {
83 auto [token, next] = tokenize(args);
84 if (token.empty())
85 return std::nullopt;
86 val = token;
87 return next;
88 }
89 };
90
91 // string
92 template <>
93 struct ArgInfo<std::string, void>
94 {
95 static ChatCommandResult TryConsume(std::string& val, ChatHandler const* handler, std::string_view args)
96 {
97 std::string_view view;
99 if (next)
100 val.assign(view);
101 return next;
102 }
103 };
104
105 // wstring
106 template <>
107 struct ArgInfo<std::wstring, void>
108 {
109 static ChatCommandResult TryConsume(std::wstring& val, ChatHandler const* handler, std::string_view args)
110 {
111 std::string_view utf8view;
112 ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(utf8view, handler, args);
113
114 if (next)
115 {
116 if (Utf8toWStr(utf8view, val))
117 return next;
118 else
120 }
121 else
122 return std::nullopt;
123 }
124 };
125
126 // enum
127 template <typename T>
128 struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>>
129 {
130 using SearchMap = std::map<std::string_view, Optional<T>, StringCompareLessI_T>;
132 {
133 SearchMap map;
134 for (T val : EnumUtils::Iterate<T>())
135 {
136 EnumText text = EnumUtils::ToString(val);
137
138 std::string_view title(text.Title);
139 std::string_view constant(text.Constant);
140
141 auto [constantIt, constantNew] = map.try_emplace(title, val);
142 if (!constantNew)
143 constantIt->second = std::nullopt;
144
145 if (title != constant)
146 {
147 auto [titleIt, titleNew] = map.try_emplace(title, val);
148 if (!titleNew)
149 titleIt->second = std::nullopt;
150 }
151 }
152 return map;
153 }
154
155 static inline SearchMap const _map = MakeSearchMap();
156
157 static T const* Match(std::string_view s)
158 {
159 auto it = _map.lower_bound(s);
160 if (it == _map.end() || !StringStartsWithI(it->first, s)) // not a match
161 return nullptr;
162
163 if (!StringEqualI(it->first, s)) // we don't have an exact match - check if it is unique
164 {
165 auto it2 = it;
166 ++it2;
167 if ((it2 != _map.end()) && StringStartsWithI(it2->first, s)) // not unique
168 return nullptr;
169 }
170
171 if (it->second)
172 return &*it->second;
173 else
174 return nullptr;
175 }
176
177 static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args)
178 {
179 std::string_view strVal;
180 ChatCommandResult next1 = ArgInfo<std::string_view>::TryConsume(strVal, handler, args);
181 if (next1)
182 {
183 if (T const* match = Match(strVal))
184 {
185 val = *match;
186 return next1;
187 }
188 }
189
190 // Value not found. Try to parse arg as underlying type and cast it to enum type
191 using U = std::underlying_type_t<T>;
192 U uVal = 0;
193 if (ChatCommandResult next2 = ArgInfo<U>::TryConsume(uVal, handler, args))
194 {
195 if (EnumUtils::IsValid<T>(uVal))
196 {
197 val = static_cast<T>(uVal);
198 return next2;
199 }
200 }
201
202 if (next1)
203 return FormatTrinityString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(strVal), Trinity::GetTypeName<T>().c_str());
204 else
205 return next1;
206 }
207 };
208
209 // a container tag
210 template <typename T>
211 struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>>
212 {
213 static ChatCommandResult TryConsume(T& tag, ChatHandler const* handler, std::string_view args)
214 {
215 return tag.TryConsume(handler, args);
216 }
217 };
218
219 // non-empty vector
220 template <typename T>
221 struct ArgInfo<std::vector<T>, void>
222 {
223 static ChatCommandResult TryConsume(std::vector<T>& val, ChatHandler const* handler, std::string_view args)
224 {
225 val.clear();
226 ChatCommandResult next = ArgInfo<T>::TryConsume(val.emplace_back(), handler, args);
227
228 if (!next)
229 return next;
230
231 while (ChatCommandResult next2 = ArgInfo<T>::TryConsume(val.emplace_back(), handler, *next))
232 next = std::move(next2);
233
234 val.pop_back();
235 return next;
236 }
237 };
238
239 // fixed-size array
240 template <typename T, size_t N>
241 struct ArgInfo<std::array<T, N>, void>
242 {
243 static ChatCommandResult TryConsume(std::array<T, N>& val, ChatHandler const* handler, std::string_view args)
244 {
245 ChatCommandResult next = args;
246 for (T& t : val)
247 if (!(next = ArgInfo<T>::TryConsume(t, handler, *next)))
248 break;
249 return next;
250 }
251 };
252
253 // variant
254 template <typename... Ts>
256 {
257 using V = std::variant<Ts...>;
258 static constexpr size_t N = std::variant_size_v<V>;
259
260 template <size_t I>
261 static ChatCommandResult TryAtIndex([[maybe_unused]] Trinity::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] ChatHandler const* handler, [[maybe_unused]] std::string_view args)
262 {
263 if constexpr (I < N)
264 {
265 ChatCommandResult thisResult = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), handler, args);
266 if (thisResult)
267 return thisResult;
268 else
269 {
270 ChatCommandResult nestedResult = TryAtIndex<I + 1>(val, handler, args);
271 if (nestedResult || !thisResult.HasErrorMessage())
272 return nestedResult;
273 if (!nestedResult.HasErrorMessage())
274 return thisResult;
275 if (StringStartsWith(nestedResult.GetErrorMessage(), "\""))
276 return Trinity::StringFormat("\"{}\"\n{} {}", thisResult.GetErrorMessage(), GetTrinityString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage());
277 else
278 return Trinity::StringFormat("\"{}\"\n{} \"{}\"", thisResult.GetErrorMessage(), GetTrinityString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage());
279 }
280 }
281 else
282 return std::nullopt;
283 }
284
285 static ChatCommandResult TryConsume(Trinity::ChatCommands::Variant<Ts...>& val, ChatHandler const* handler, std::string_view args)
286 {
287 ChatCommandResult result = TryAtIndex<0>(val, handler, args);
288 if (result.HasErrorMessage() && (result.GetErrorMessage().find('\n') != std::string::npos))
290 return result;
291 }
292 };
293
294 // AchievementEntry* from numeric id or link
295 template <>
297 {
298 static ChatCommandResult TryConsume(AchievementEntry const*&, ChatHandler const*, std::string_view);
299 };
300
301 // CurrencyTypesEntry* from numeric id or link
302 template <>
304 {
305 static ChatCommandResult TryConsume(CurrencyTypesEntry const*&, ChatHandler const*, std::string_view);
306 };
307
308 // GameTele* from string name or link
309 template <>
311 {
312 static ChatCommandResult TryConsume(GameTele const*&, ChatHandler const*, std::string_view);
313 };
314
315 // ItemTemplate* from numeric id or link
316 template <>
318 {
319 static ChatCommandResult TryConsume(ItemTemplate const*&, ChatHandler const*, std::string_view);
320 };
321
322 // Quest* from numeric id or link
323 template <>
324 struct TC_GAME_API ArgInfo<Quest const*>
325 {
326 static ChatCommandResult TryConsume(Quest const*&, ChatHandler const*, std::string_view);
327 };
328
329 // SpellInfo const* from spell id or link
330 template <>
332 {
333 static ChatCommandResult TryConsume(SpellInfo const*&, ChatHandler const*, std::string_view);
334 };
335
336}
337
338#endif
#define TC_GAME_API
Definition: Define.h:123
#define STRING_VIEW_FMT_ARG(str)
Definition: Define.h:135
@ LANG_CMDPARSER_STRING_VALUE_INVALID
Definition: Language.h:1000
@ LANG_CMDPARSER_EITHER
Definition: Language.h:998
@ LANG_CMDPARSER_OR
Definition: Language.h:999
@ LANG_CMDPARSER_INVALID_UTF8
Definition: Language.h:1001
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition: Optional.h:25
bool StringEqualI(std::string_view a, std::string_view b)
Definition: Util.cpp:891
bool Utf8toWStr(char const *utf8str, size_t csize, wchar_t *wstr, size_t &wsize)
Definition: Util.cpp:383
bool StringStartsWith(std::string_view haystack, std::string_view needle)
Definition: Util.h:398
bool StringStartsWithI(std::string_view haystack, std::string_view needle)
Definition: Util.h:399
static EnumText ToString(Enum value)
Definition: SmartEnum.h:53
TokenizeResult tokenize(std::string_view args)
std::string FormatTrinityString(ChatHandler const *handler, TrinityStrings which, Ts &&... args)
TC_GAME_API char const * GetTrinityString(ChatHandler const *handler, TrinityStrings which)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
Definition: StringFormat.h:38
STL namespace.
char const *const Constant
Definition: SmartEnum.h:28
char const *const Title
Definition: SmartEnum.h:30
static ChatCommandResult TryConsume(AchievementEntry const *&, ChatHandler const *, std::string_view)
static ChatCommandResult TryConsume(CurrencyTypesEntry const *&, ChatHandler const *, std::string_view)
static ChatCommandResult TryConsume(GameTele const *&, ChatHandler const *, std::string_view)
static ChatCommandResult TryConsume(ItemTemplate const *&, ChatHandler const *, std::string_view)
static ChatCommandResult TryConsume(Quest const *&, ChatHandler const *, std::string_view)
static ChatCommandResult TryConsume(SpellInfo const *&, ChatHandler const *, std::string_view)
static ChatCommandResult TryConsume(T &val, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(T &tag, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(T &val, ChatHandler const *handler, std::string_view args)
std::map< std::string_view, Optional< T >, StringCompareLessI_T > SearchMap
static ChatCommandResult TryAtIndex(Trinity::ChatCommands::Variant< Ts... > &val, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(Trinity::ChatCommands::Variant< Ts... > &val, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(std::array< T, N > &val, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(std::string &val, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(std::string_view &val, ChatHandler const *, std::string_view args)
static ChatCommandResult TryConsume(std::vector< T > &val, ChatHandler const *handler, std::string_view args)
static ChatCommandResult TryConsume(std::wstring &val, ChatHandler const *handler, std::string_view args)