VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
Args.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <array>
5#include <chrono>
6#include <concepts>
7#include <cstddef>
8#include <cstdint>
9#include <optional>
10#include <string>
11#include <tuple>
12#include <utility>
13#include <variant>
14#include <vector>
15
16namespace VoltMod
17{
18
19/**
20 * @brief Which binder runs for one declared argument.
21 *
22 * Derived from the handler's parameter list; plugins never write one. The enumerator name is also
23 * the suffix of the argument's usage-placeholder key (`ArgKind::Target` -> `cmd.usage.target`), so
24 * adding a kind adds one translation key and no switch.
25 *
26 * Declaration order is the order of @ref Args::All and of @ref BoundArg's alternatives; a
27 * static_assert below holds the three together.
28 */
29enum class ArgKind : uint8_t
30{
31 Target,
32 Targets,
34 SteamId,
36 Int,
37 U64,
38 Word,
39 Rest,
40};
41
42/**
43 * @brief The argument types a command handler's parameter list is written in.
44 *
45 * A handler's signature *is* its argument spec: `Run([](Caller c, Args::Target t,
46 * Args::Duration d) { ... })` declares two arguments, in that order, and the framework parses,
47 * validates and binds them before the handler runs.
48 *
49 * The one nested namespace of types in the framework, because `Target`, `Int`, `Word` and `Rest`
50 * are far too generic to carry at `VoltMod::` scope, and they appear nowhere but in a handler's
51 * parameter list.
52 *
53 * Each type names its own @ref ArgKind, which is all the framework needs to know about it: there
54 * is no separate trait table to keep in step.
55 */
56namespace Args
57{
58
59/** One online player, resolved through the selector grammar and filtered by `Policy::Authorize`. */
60struct Target
61{
62 static constexpr ArgKind Kind = ArgKind::Target;
63 Player* Value = nullptr;
64
65 Player* operator->() const { return Value; }
66};
67
68/**
69 * Every online player one token names, filtered by `Policy::Authorize`.
70 *
71 * The multi-target counterpart of @ref Target: `@all`, `@t`, `@ct` and the rest bind here, where
72 * @ref Target rejects them because it can only yield one player. Never empty - a selector that
73 * matches nobody the caller may act on fails to bind instead.
74 */
75struct Targets
76{
77 static constexpr ArgKind Kind = ArgKind::Targets;
78 std::vector<Player*> Value;
79};
80
81/** The @ref ParseDuration grammar (`30s`/`5m`/`2h`/`7d`/`perm`). A bare number is minutes, and
82 * zero means permanent. */
84{
85 static constexpr ArgKind Kind = ArgKind::Duration;
86 std::chrono::seconds Value{};
87};
88
89/** A numeric SteamID64. */
90struct SteamId
91{
92 static constexpr ArgKind Kind = ArgKind::SteamId;
94};
95
96/**
97 * An online player when the token resolves to one, otherwise the bare SteamID64.
98 *
99 * Resolution is tried first, so `@me` and a name fragment both work; a numeric token that matches
100 * nobody online (or nobody the caller may act on) falls back to @ref SteamId with @ref Online left
101 * null, which is how an offline player is addressed.
102 */
104{
106 Player* Online = nullptr;
108};
109
110struct Int
111{
112 static constexpr ArgKind Kind = ArgKind::Int;
113 int Value = 0;
114};
115
116/** A non-negative 64-bit id: a workshop id, or anything else too wide for @ref Int. */
117struct U64
118{
119 static constexpr ArgKind Kind = ArgKind::U64;
121};
122
123/** One verbatim token. `"two words"` in the message is one token. */
124struct Word
125{
126 static constexpr ArgKind Kind = ArgKind::Word;
127 std::string Value;
128};
129
130/** The remainder of the line, tokens rejoined with single spaces. Only the last argument may be a
131 * Rest, and it is what makes trailing free text (a reason) possible. */
132struct Rest
133{
134 static constexpr ArgKind Kind = ArgKind::Rest;
135 std::string Value;
136};
137
138/** An argument the caller may omit. Only trailing arguments may be Opt, and an Opt may not wrap
139 * another Opt. */
140template <class T>
141struct Opt
142{
143 std::optional<T> Value;
144
145 /** The argument's own value, or @p fallback when the caller omitted it. */
146 template <class U = T>
147 decltype(U::Value) ValueOr(decltype(U::Value) fallback) const
148 {
149 return Value ? Value->Value : std::move(fallback);
150 }
151};
152
153/** Every argument type, in @ref ArgKind order. The one list: @ref BoundArg takes its alternatives
154 * from it, and the static_assert below checks it against the enum. */
155using All = std::tuple<Target, Targets, Duration, SteamId, PlayerOrSteamId, Int, U64, Word, Rest>;
156
157} // namespace Args
158
159/** One entry of a command's derived argument descriptor. */
161{
163 bool Optional = false;
164};
165
166/** The variant behind @ref BoundArg, over a list of argument types. */
167template <class Tuple>
169
170template <class... A>
171struct BoundArgFor<std::tuple<A...>>
172{
173 using Type = std::variant<std::monostate, A...>;
174};
175
176/** One bound argument. `std::monostate` is an optional argument the caller omitted. */
178
179/** Whether every type in @ref Args::All sits at the index its own @ref ArgKind names. */
180template <std::size_t... I>
181consteval bool ArgKindsMatchOrder(std::index_sequence<I...>)
182{
183 return ((static_cast<std::size_t>(std::tuple_element_t<I, Args::All>::Kind) == I) && ...);
184}
185
186static_assert(ArgKindsMatchOrder(std::make_index_sequence<std::tuple_size_v<Args::All>>{}),
187 "Args::All must list the argument types in ArgKind declaration order.");
188
189/** Whether @p T is an @ref Args::Opt. */
190template <class T>
191inline constexpr bool IsOptionalArg = false;
192template <class T>
193inline constexpr bool IsOptionalArg<Args::Opt<T>> = true;
194
195/** A type that names its own @ref ArgKind: every type in @ref Args::All, and nothing else. */
196template <class T>
197concept NamesArgKind = requires {
198 { T::Kind } -> std::convertible_to<ArgKind>;
199};
200
201/**
202 * @brief What one handler parameter type means to the framework.
203 *
204 * The primary is declared and never defined, so a stray parameter type leaves `ArgTrait<T>`
205 * incomplete: @ref CommandArg then reads as false instead of hard-erroring, and the compiler names
206 * the offending signature.
207 */
208template <class T>
209struct ArgTrait;
210
211/** Every @ref Args type binds as itself and is required. */
212template <NamesArgKind T>
213struct ArgTrait<T>
214{
215 static constexpr ArgKind Kind = T::Kind;
216 static constexpr bool Optional = false;
217 using Bound = T;
218};
219
220/** `Opt<T>` binds exactly like `T` and may be missing. */
221template <class T>
222struct ArgTrait<Args::Opt<T>>
223{
224 static_assert(!IsOptionalArg<T>, "Args::Opt cannot wrap another Args::Opt; one marks the argument optional.");
225
226 static constexpr ArgKind Kind = ArgTrait<T>::Kind;
227 static constexpr bool Optional = true;
228 using Bound = typename ArgTrait<T>::Bound;
229};
230
231/** A type usable as a command handler parameter. */
232template <class T>
233concept CommandArg = requires {
234 { ArgTrait<T>::Kind } -> std::convertible_to<ArgKind>;
235};
236
237/** Whether every optional argument in @p A trails the required ones. */
238template <class... A>
239consteval bool OptionalsTrail()
240{
241 const std::array<bool, sizeof...(A)> optional{ArgTrait<A>::Optional...};
242 bool seen = false;
243 for (bool isOptional : optional)
244 {
245 if (isOptional)
246 {
247 seen = true;
248 }
249 else if (seen)
250 {
251 return false;
252 }
253 }
254 return true;
255}
256
257/** Whether at most one @ref Args::Rest appears in @p A, as the final argument. A Rest eats the
258 * remainder of the line, so anything after it could never be reached. */
259template <class... A>
260consteval bool RestIsLast()
261{
262 const std::array<ArgKind, sizeof...(A)> kinds{ArgTrait<A>::Kind...};
263 for (std::size_t i = 0; i + 1 < kinds.size(); ++i)
264 {
265 if (kinds[i] == ArgKind::Rest)
266 {
267 return false;
268 }
269 }
270 return true;
271}
272
273/** A whole parameter list the builder accepts. Written as a concept so a test can assert that an
274 * invalid signature is rejected without compiling the invalid call. */
275template <class... A>
277
278/** The descriptor the router binds against, derived from the handler's parameter list. */
279template <class... A>
280std::vector<ArgDesc> DescribeArgs()
281{
282 return {ArgDesc{.Kind = ArgTrait<A>::Kind, .Optional = ArgTrait<A>::Optional}...};
283}
284
285/** Recovers one handler parameter from its bound slot. */
286template <class T>
288{
289 static T From(const BoundArg& bound) { return std::get<T>(bound); }
290};
291
292template <class T>
293struct ArgUnpack<Args::Opt<T>>
294{
296 {
297 if (std::holds_alternative<std::monostate>(bound))
298 {
299 return {};
300 }
301 return Args::Opt<T>{std::get<T>(bound)};
302 }
303};
304
305} // namespace VoltMod
One connected player, owned by PlayerManager for the length of the connection.
Definition Player.hpp:23
Static utilities for converting between SteamID formats (64-bit, SteamID2, SteamID3).
Definition SteamId.hpp:12
std::tuple< Target, Targets, Duration, SteamId, PlayerOrSteamId, Int, U64, Word, Rest > All
Definition Args.hpp:155
constexpr bool IsOptionalArg
Definition Args.hpp:191
typename BoundArgFor< Args::All >::Type BoundArg
Definition Args.hpp:177
consteval bool RestIsLast()
Definition Args.hpp:260
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
ArgKind
Which binder runs for one declared argument.
Definition Args.hpp:30
std::vector< ArgDesc > DescribeArgs()
Definition Args.hpp:280
consteval bool OptionalsTrail()
Definition Args.hpp:239
consteval bool ArgKindsMatchOrder(std::index_sequence< I... >)
Definition Args.hpp:181
ArgKind Kind
Definition Args.hpp:162
typename ArgTrait< T >::Bound Bound
Definition Args.hpp:228
What one handler parameter type means to the framework.
Definition Args.hpp:209
static Args::Opt< T > From(const BoundArg &bound)
Definition Args.hpp:295
static T From(const BoundArg &bound)
Definition Args.hpp:289
std::chrono::seconds Value
Definition Args.hpp:86
static constexpr ArgKind Kind
Definition Args.hpp:85
static constexpr ArgKind Kind
Definition Args.hpp:112
decltype(U::Value) ValueOr(decltype(U::Value) fallback) const
Definition Args.hpp:147
std::optional< T > Value
Definition Args.hpp:143
static constexpr ArgKind Kind
Definition Args.hpp:105
static constexpr ArgKind Kind
Definition Args.hpp:134
std::string Value
Definition Args.hpp:135
static constexpr ArgKind Kind
Definition Args.hpp:92
Player * operator->() const
Definition Args.hpp:65
static constexpr ArgKind Kind
Definition Args.hpp:62
static constexpr ArgKind Kind
Definition Args.hpp:77
std::vector< Player * > Value
Definition Args.hpp:78
uint64_t Value
Definition Args.hpp:120
static constexpr ArgKind Kind
Definition Args.hpp:119
static constexpr ArgKind Kind
Definition Args.hpp:126
std::string Value
Definition Args.hpp:127
std::variant< std::monostate, A... > Type
Definition Args.hpp:173