330 lines
12 KiB
C++
330 lines
12 KiB
C++
/*
|
|
* This file is part of the AzerothCore 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 Affero General Public License as published by the
|
|
* Free Software Foundation; either version 3 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 Affero 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/>.
|
|
*/
|
|
|
|
#ifndef _CHATCOMMANDARGS_H
|
|
#define _CHATCOMMANDARGS_H
|
|
|
|
#include "ChatCommandHelpers.h"
|
|
#include "ChatCommandTags.h"
|
|
#include "SmartEnum.h"
|
|
#include "StringConvert.h"
|
|
#include "StringFormat.h"
|
|
#include "Util.h"
|
|
#include <charconv>
|
|
#include <map>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
struct GameTele;
|
|
|
|
namespace Acore::Impl::ChatCommands
|
|
{
|
|
|
|
/************************** ARGUMENT HANDLERS *******************************************\
|
|
|* Define how to extract contents of a certain requested type from a string *|
|
|
|* Must implement the following: *|
|
|
|* - TryConsume: T&, ChatHandler const*, std::string_view -> ChatCommandResult *|
|
|
|* - 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) *|
|
|
|* *|
|
|
|* - 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>
|
|
struct ArgInfo { static_assert(Acore::dependant_false_v<T>, "Invalid command parameter type - see ChatCommandArgs.h for possible types"); };
|
|
|
|
// catch-all for number types
|
|
template <typename T>
|
|
struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>>
|
|
{
|
|
static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
auto [token, tail] = tokenize(args);
|
|
if (token.empty())
|
|
return std::nullopt;
|
|
|
|
if (Optional<T> v = StringTo<T>(token, 0))
|
|
val = *v;
|
|
else
|
|
return FormatAcoreString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), GetTypeName<T>().c_str());
|
|
|
|
if constexpr (std::is_floating_point_v<T>)
|
|
{
|
|
if (!std::isfinite(val))
|
|
return FormatAcoreString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), GetTypeName<T>().c_str());
|
|
}
|
|
|
|
return tail;
|
|
}
|
|
};
|
|
|
|
// string_view
|
|
template <>
|
|
struct ArgInfo<std::string_view, void>
|
|
{
|
|
static ChatCommandResult TryConsume(std::string_view& val, ChatHandler const*, std::string_view args)
|
|
{
|
|
auto [token, next] = tokenize(args);
|
|
if (token.empty())
|
|
return std::nullopt;
|
|
val = token;
|
|
return next;
|
|
}
|
|
};
|
|
|
|
// string
|
|
template <>
|
|
struct ArgInfo<std::string, void>
|
|
{
|
|
static ChatCommandResult TryConsume(std::string& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
std::string_view view;
|
|
ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(view, handler, args);
|
|
if (next)
|
|
val.assign(view);
|
|
return next;
|
|
}
|
|
};
|
|
|
|
// wstring
|
|
template <>
|
|
struct ArgInfo<std::wstring, void>
|
|
{
|
|
static ChatCommandResult TryConsume(std::wstring& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
std::string_view utf8view;
|
|
ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(utf8view, handler, args);
|
|
|
|
if (next)
|
|
{
|
|
if (Utf8toWStr(utf8view, val))
|
|
return next;
|
|
else
|
|
return GetAcoreString(handler, LANG_CMDPARSER_INVALID_UTF8);
|
|
}
|
|
else
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
|
|
// enum
|
|
template <typename T>
|
|
struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>>
|
|
{
|
|
using SearchMap = std::map<std::string_view, Optional<T>, StringCompareLessI_T>;
|
|
static SearchMap MakeSearchMap()
|
|
{
|
|
SearchMap map;
|
|
for (T val : EnumUtils::Iterate<T>())
|
|
{
|
|
EnumText text = EnumUtils::ToString(val);
|
|
|
|
std::string_view title(text.Title);
|
|
std::string_view constant(text.Constant);
|
|
|
|
auto [constantIt, constantNew] = map.try_emplace(title, val);
|
|
if (!constantNew)
|
|
constantIt->second = std::nullopt;
|
|
|
|
if (title != constant)
|
|
{
|
|
auto [titleIt, titleNew] = map.try_emplace(title, val);
|
|
if (!titleNew)
|
|
titleIt->second = std::nullopt;
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
static inline SearchMap const _map = MakeSearchMap();
|
|
|
|
static T const* Match(std::string_view s)
|
|
{
|
|
auto it = _map.lower_bound(s);
|
|
if (it == _map.end() || !StringStartsWithI(it->first, s)) // not a match
|
|
return nullptr;
|
|
|
|
if (!StringEqualI(it->first, s)) // we don't have an exact match - check if it is unique
|
|
{
|
|
auto it2 = it;
|
|
++it2;
|
|
if ((it2 != _map.end()) && StringStartsWithI(it2->first, s)) // not unique
|
|
return nullptr;
|
|
}
|
|
|
|
if (it->second)
|
|
return &*it->second;
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
std::string_view strVal;
|
|
ChatCommandResult next1 = ArgInfo<std::string_view>::TryConsume(strVal, handler, args);
|
|
if (next1)
|
|
{
|
|
if (T const* match = Match(strVal))
|
|
{
|
|
val = *match;
|
|
return next1;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
if (ChatCommandResult next2 = ArgInfo<U>::TryConsume(uVal, handler, args))
|
|
{
|
|
if (EnumUtils::IsValid<T>(uVal))
|
|
{
|
|
val = static_cast<T>(uVal);
|
|
return next2;
|
|
}
|
|
}
|
|
|
|
if (next1)
|
|
return FormatAcoreString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(strVal), GetTypeName<T>().c_str());
|
|
else
|
|
return next1;
|
|
}
|
|
};
|
|
|
|
// a container tag
|
|
template <typename T>
|
|
struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>>
|
|
{
|
|
static ChatCommandResult TryConsume(T& tag, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
return tag.TryConsume(handler, args);
|
|
}
|
|
};
|
|
|
|
// non-empty vector
|
|
template <typename T>
|
|
struct ArgInfo<std::vector<T>, void>
|
|
{
|
|
static ChatCommandResult TryConsume(std::vector<T>& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
val.clear();
|
|
ChatCommandResult next = ArgInfo<T>::TryConsume(val.emplace_back(), handler, args);
|
|
|
|
if (!next)
|
|
return next;
|
|
|
|
while (ChatCommandResult next2 = ArgInfo<T>::TryConsume(val.emplace_back(), handler, *next))
|
|
next = std::move(next2);
|
|
|
|
val.pop_back();
|
|
return next;
|
|
}
|
|
};
|
|
|
|
// fixed-size array
|
|
template <typename T, size_t N>
|
|
struct ArgInfo<std::array<T, N>, void>
|
|
{
|
|
static ChatCommandResult TryConsume(std::array<T, N>& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
ChatCommandResult next = args;
|
|
for (T& t : val)
|
|
if (!(next = ArgInfo<T>::TryConsume(t, handler, *next)))
|
|
break;
|
|
return next;
|
|
}
|
|
};
|
|
|
|
// variant
|
|
template <typename... Ts>
|
|
struct ArgInfo<Acore::ChatCommands::Variant<Ts...>>
|
|
{
|
|
using V = std::variant<Ts...>;
|
|
static constexpr size_t N = std::variant_size_v<V>;
|
|
|
|
template <size_t I>
|
|
static ChatCommandResult TryAtIndex([[maybe_unused]] Acore::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] ChatHandler const* handler, [[maybe_unused]] std::string_view args)
|
|
{
|
|
if constexpr (I < N)
|
|
{
|
|
ChatCommandResult thisResult = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), handler, args);
|
|
if (thisResult)
|
|
return thisResult;
|
|
else
|
|
{
|
|
ChatCommandResult nestedResult = TryAtIndex<I + 1>(val, handler, args);
|
|
if (nestedResult || !thisResult.HasErrorMessage())
|
|
return nestedResult;
|
|
if (!nestedResult.HasErrorMessage())
|
|
return thisResult;
|
|
if (StringStartsWith(nestedResult.GetErrorMessage(), "\""))
|
|
return Acore::StringFormat("\"%s\"\n%s %s", thisResult.GetErrorMessage().c_str(), GetAcoreString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage().c_str());
|
|
else
|
|
return Acore::StringFormat("\"%s\"\n%s \"%s\"", thisResult.GetErrorMessage().c_str(), GetAcoreString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage().c_str());
|
|
}
|
|
}
|
|
else
|
|
return std::nullopt;
|
|
}
|
|
|
|
static ChatCommandResult TryConsume(Acore::ChatCommands::Variant<Ts...>& val, ChatHandler const* handler, std::string_view args)
|
|
{
|
|
ChatCommandResult result = TryAtIndex<0>(val, handler, args);
|
|
if (result.HasErrorMessage() && (result.GetErrorMessage().find('\n') != std::string::npos))
|
|
return Acore::StringFormat("%s %s", GetAcoreString(handler, LANG_CMDPARSER_EITHER), result.GetErrorMessage().c_str());
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// AchievementEntry* from numeric id or link
|
|
template <>
|
|
struct AC_GAME_API ArgInfo<AchievementEntry const*>
|
|
{
|
|
static ChatCommandResult TryConsume(AchievementEntry const*&, ChatHandler const*, std::string_view);
|
|
};
|
|
|
|
// GameTele* from string name or link
|
|
template <>
|
|
struct AC_GAME_API ArgInfo<GameTele const*>
|
|
{
|
|
static ChatCommandResult TryConsume(GameTele const*&, ChatHandler const*, std::string_view);
|
|
};
|
|
|
|
// ItemTemplate* from numeric id or link
|
|
template <>
|
|
struct AC_GAME_API ArgInfo<ItemTemplate const*>
|
|
{
|
|
static ChatCommandResult TryConsume(ItemTemplate const*&, ChatHandler const*, std::string_view);
|
|
};
|
|
|
|
// SpellInfo const* from spell id or link
|
|
template <>
|
|
struct AC_GAME_API ArgInfo<SpellInfo const*>
|
|
{
|
|
static ChatCommandResult TryConsume(SpellInfo const*&, ChatHandler const*, std::string_view);
|
|
};
|
|
|
|
// Quest const* from quest id or link
|
|
template <>
|
|
struct AC_GAME_API ArgInfo<Quest const*>
|
|
{
|
|
static ChatCommandResult TryConsume(Quest const*&, ChatHandler const*, std::string_view);
|
|
};
|
|
}
|
|
|
|
#endif
|