cli20 0.1.2
A C++20 typed-schema command-line parser
Loading...
Searching...
No Matches
argument.hh
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <filesystem>
5#include <functional>
6#include <optional>
7#include <string>
8#include <string_view>
9#include <type_traits>
10#include <vector>
11
12#include "cli/action.hh"
13#include "cli/argument_base.hh"
15#include "cli/meta.hh"
16#include "cli/relation.hh"
17#include "cli/string_literal.hh"
18
19namespace cli {
20
37template <class T>
38concept ArgumentSpec = requires {
39 requires detail::members_are_derived_from_valid_cli_class<T>();
40 requires detail::options_have_unique_long_name<T>();
41 requires detail::options_have_unique_short_name<T>();
42 requires detail::commands_have_unique_long_name<T>();
43 requires detail::positionals_have_variadic_at_end<T>();
44 requires detail::relations_are_well_formed<T>();
45};
46
47template <ArgumentSpec T>
48struct Parser;
49
65template <class T>
67 std::string_view help{};
69 std::optional<T>
71 std::function<void(const T&)>
73 std::string_view env{};
75 bool hidden{};
76 std::string_view
78};
79
80namespace detail {
81
82template <StringLiteral Name, char ShortName, Nargs N, Action A>
83 requires requires {
84 detail::is_valid_long_option_name<Name>();
85 detail::is_valid_short_option_name(ShortName);
86 0 <= N.min;
87 -1 <= N.max;
88 (N.max == -1 || N.min <= N.max);
89 }
90struct ArgImpl : public OptionTag {
91 using value_type =
92 typename std::remove_cvref_t<decltype(std::get<1>(A.validate()))>::type;
93 ArgImpl() : ArgImpl(ArgParameter<value_type>{}) {}
94 ArgImpl(ArgParameter<value_type> param)
95 : param_(std::move(param)),
96 value_(param_.default_value.value_or(value_type{})) {}
97
98 // Convenience constructor for StoreInto variants (value_type == T*).
99 ArgImpl(std::remove_pointer_t<value_type>& ref)
100 requires std::is_pointer_v<value_type>
101 : value_(&ref) {}
102 ArgImpl(std::remove_pointer_t<value_type>& ref, ArgParameter<value_type> param)
103 requires std::is_pointer_v<value_type>
104 : param_(param), value_(&ref) {}
105
106 // Bind to an external variable after construction.
107 // Allows: opt.bind(var) or opt = &var
108 auto bind(std::remove_pointer_t<value_type>& ref) -> ArgImpl&
109 requires std::is_pointer_v<value_type>
110 {
111 value_ = &ref;
112 return *this;
113 }
114 auto operator=(std::remove_pointer_t<value_type>* ptr) -> ArgImpl&
115 requires std::is_pointer_v<value_type>
116 {
117 value_ = ptr;
118 return *this;
119 }
120
121 static constexpr auto name = Name;
122 static constexpr auto short_name = ShortName;
123 static constexpr auto nargs = N;
124
125 [[nodiscard]] auto value() const -> const value_type& { return value_; }
126 [[nodiscard]] auto value() -> value_type& { return value_; }
127 [[nodiscard]] auto provided() const -> bool { return provided_; }
128 [[nodiscard]] auto occurrences() const -> std::size_t {
129 return occurrence_count_;
130 }
131 [[nodiscard]] explicit operator bool() const { return value_present_; }
132 [[nodiscard]] auto operator*() -> value_type& { return value_; }
133 [[nodiscard]] auto operator*() const -> const value_type& { return value_; }
134 [[nodiscard]] auto operator->() -> value_type* { return &value_; }
135 [[nodiscard]] auto operator->() const -> const value_type* { return &value_; }
136
137 template <class U>
138 [[nodiscard]] auto value_or(U&& fallback) const -> value_type {
139 if (value_present_) return value_;
140 return static_cast<value_type>(std::forward<U>(fallback));
141 }
142
143 [[nodiscard]] explicit operator std::optional<value_type>() const {
144 if (value_present_) return value_;
145 return std::nullopt;
146 }
147
148 [[nodiscard]] auto help_text() const -> const std::string_view {
149 return param_.help;
150 }
151 [[nodiscard]] auto hidden() const -> bool { return param_.hidden; }
152 [[nodiscard]] auto deprecated() const -> std::string_view {
153 return param_.deprecated;
154 }
155 [[nodiscard]] auto presence() const -> Presence { return param_.presence; }
156 [[nodiscard]] auto env() const -> std::string_view { return param_.env; }
157 [[nodiscard]] auto default_value() const -> const std::optional<value_type>& {
158 return param_.default_value;
159 }
160
161 protected:
162 static constexpr bool entry_is_optional =
163 std::remove_cvref_t<decltype(A)>::entry_is_optional;
164
165 // Called once when the option token is seen (before processing its values).
166 auto notify_option_seen() -> void { ++occurrence_count_; }
167
168 // Called once per value token associated with this option.
169 auto invoke_action(std::optional<std::string_view> text, std::size_t arg_index,
170 std::size_t occurrence_nargs, std::size_t value_index,
171 bool mark_provided = true) -> ActionResult<void> {
172 if (mark_provided) provided_ = true;
173 ActionCtx<value_type> ctx{
174 .index = arg_index,
175 .occurrences = occurrence_count_,
176 .nargs = occurrence_nargs,
177 .value_index = value_index,
178 .total_values = total_values_,
179 .arg = std::ref(value_),
180 };
181 if (text.has_value()) ++total_values_;
182
183 auto invoke_result = [&ctx, &text]() -> auto {
184 if constexpr (entry_is_optional) {
185 return decltype(A)::invoke(
186 ctx, ActionResult<std::optional<std::string_view>>::ok(text));
187 } else {
188 return decltype(A)::invoke(
189 ctx, ActionResult<std::string_view>::ok(text.value_or("")));
190 }
191 }();
192 if (invoke_result.has_error()) {
193 return ActionResult<void>::fail(std::move(invoke_result.error));
194 }
195 if (invoke_result.has_value()) value_present_ = true;
196 return ActionResult<void>::ok();
197 }
198
199 // Called once after all value tokens for one option occurrence are processed.
200 auto on_parse() -> void {
201 if (param_.on_parse) param_.on_parse(value_);
202 }
203
204 [[nodiscard]] auto get_param() const -> const ArgParameter<value_type>& {
205 return param_;
206 }
207
208 template <ArgumentSpec>
209 friend struct ::cli::Parser;
210
211 private:
212 std::size_t total_values_{}; // total values across all occurrences
213 ArgParameter<value_type> param_;
214 value_type value_{};
215 std::size_t occurrence_count_{}; // times the option token appeared
216 bool provided_{};
217 bool value_present_{param_.default_value.has_value()};
218};
219
220template <auto A>
221concept ActionValue = requires { std::remove_cvref_t<decltype(A)>::validate(); };
222
223template <auto... Args>
224struct ArgAlias {
225 static_assert(
226 sizeof...(Args) && !sizeof...(Args),
227 "Unsupported Arg<> parameter combination.\n"
228 "Supported forms (after the Name parameter):\n"
229 " Arg<Name, Action>\n"
230 " Arg<Name, Action, Nargs>\n"
231 " Arg<Name, ShortName, Action>\n"
232 " Arg<Name, ShortName, Action, Nargs>\n"
233 "Consider using Flag, Option, or ListOption for common argument kinds.");
234};
235
236template <StringLiteral Name, auto A>
237 requires ActionValue<A>
238struct ArgAlias<Name, A> {
239 using type = ArgImpl<Name, '\0', nargs::one, A>;
240};
241
242template <StringLiteral Name, auto A, auto N>
243 requires(ActionValue<A> &&
244 std::same_as<std::remove_cvref_t<decltype(N)>, Nargs>)
245struct ArgAlias<Name, A, N> {
246 using type = ArgImpl<Name, '\0', N, A>;
247};
248
249template <StringLiteral Name, char ShortName, auto A>
250 requires ActionValue<A>
251struct ArgAlias<Name, ShortName, A> {
252 using type = ArgImpl<Name, ShortName, nargs::one, A>;
253};
254
255template <StringLiteral Name, char ShortName, auto A, auto N>
256 requires(ActionValue<A> &&
257 std::same_as<std::remove_cvref_t<decltype(N)>, Nargs>)
258struct ArgAlias<Name, ShortName, A, N> {
259 using type = ArgImpl<Name, ShortName, N, A>;
260};
261
262} // namespace detail
263
264template <Nargs N, Action A>
265 requires requires {
266 0 <= N.min;
267 -1 <= N.max;
268 (N.max == -1 || N.min <= N.max);
269 }
272 typename std::remove_cvref_t<decltype(std::get<1>(A.validate()))>::type;
273
276 : value_(param.default_value.value_or(value_type{})),
277 param_(std::move(param)) {}
278
279 static constexpr auto nargs = N;
280
281 // Called once after all tokens for this positional are consumed.
282 auto on_parse() -> void {
283 if (param_.on_parse) param_.on_parse(value_);
284 }
285
286 [[nodiscard]] auto value() const -> const value_type& { return value_; }
287 [[nodiscard]] auto value() -> value_type& { return value_; }
288 [[nodiscard]] auto provided() const -> bool { return provided_; }
289 [[nodiscard]] auto occurrences() const -> std::size_t {
290 return occurrence_count_;
291 }
292 [[nodiscard]] explicit operator bool() const { return value_present_; }
293 [[nodiscard]] auto operator*() -> value_type& { return value_; }
294 [[nodiscard]] auto operator*() const -> const value_type& { return value_; }
295 [[nodiscard]] auto operator->() -> value_type* { return &value_; }
296 [[nodiscard]] auto operator->() const -> const value_type* { return &value_; }
297
298 template <class U>
299 [[nodiscard]] auto value_or(U&& fallback) const -> value_type {
300 if (value_present_) return value_;
301 return static_cast<value_type>(std::forward<U>(fallback));
302 }
303
304 [[nodiscard]] explicit operator std::optional<value_type>() const {
305 if (value_present_) return value_;
306 return std::nullopt;
307 }
308
309 [[nodiscard]] auto help_text() const -> const std::string_view {
310 return param_.help;
311 }
312 [[nodiscard]] auto hidden() const -> bool { return param_.hidden; }
313 [[nodiscard]] auto deprecated() const -> std::string_view {
314 return param_.deprecated;
315 }
316 [[nodiscard]] auto presence() const -> Presence { return param_.presence; }
317 [[nodiscard]] auto env() const -> std::string_view { return param_.env; }
318 [[nodiscard]] auto default_value() const -> const std::optional<value_type>& {
319 return param_.default_value;
320 }
321
322 protected:
323 [[nodiscard]] auto get_param() const -> const ArgParameter<value_type>& {
324 return param_;
325 }
326
327 static constexpr bool entry_is_optional =
328 std::remove_cvref_t<decltype(A)>::entry_is_optional;
329
330 // Called once per value token for this positional.
331 auto invoke_action(std::string_view text, std::size_t arg_index,
332 std::size_t occurrence_nargs = 1,
333 std::size_t value_index = 0) -> ActionResult<void> {
334 provided_ = true;
335 ++occurrence_count_;
337 .index = arg_index,
338 .occurrences = occurrence_count_,
339 .nargs = occurrence_nargs,
340 .value_index = value_index,
341 .total_values = total_values_,
342 .arg = std::ref(value_),
343 };
344 ++total_values_;
345 auto result =
346 decltype(A)::invoke(ctx, ActionResult<std::string_view>::ok(text));
347 if (result.has_error()) {
348 return ActionResult<void>::fail(std::move(result.error));
349 }
350 if (result.has_value()) value_present_ = true;
351 return ActionResult<void>::ok();
352 }
353
354 private:
355 template <ArgumentSpec>
356 friend struct Parser;
357
358 value_type value_{};
360 std::size_t occurrence_count_{}; // times a value was provided
361 std::size_t total_values_{}; // total values across all invocations
362 bool provided_{};
363 bool value_present_{param_.default_value.has_value()};
364};
365
380 std::string_view text{};
381 constexpr Description() = default;
382 template <std::size_t N>
383 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
384 constexpr Description(const char (&s)[N]) : text(s, N - 1) {}
385 constexpr Description(std::string_view) = delete;
386 constexpr operator std::string_view() const { return text; }
387};
388
393 std::string_view
395 bool hidden{};
396 std::string_view
398};
399
411template <StringLiteral Name, ArgumentSpec T>
412 requires requires { detail::is_valid_command_name<Name>(); }
413struct Command : public T, public CommandTag {
414 using T::T;
415 using argument_type = T;
416
417 Command() = default;
419 : help_(param.help),
420 hidden_(param.hidden),
421 deprecated_(param.deprecated) {}
422 static constexpr auto name = Name;
423 static constexpr auto command_name() -> std::string_view {
424 return Name.view();
425 }
426 [[nodiscard]] auto provided() const -> bool { return provided_; }
427 [[nodiscard]] auto help_text() const -> std::string_view { return help_; }
428 [[nodiscard]] auto hidden() const -> bool { return hidden_; }
429 [[nodiscard]] auto deprecated() const -> std::string_view {
430 return deprecated_;
431 }
432 auto mark_provided() -> void { provided_ = true; }
433
434 private:
435 std::string_view help_{};
436 bool hidden_{};
437 std::string_view deprecated_{};
438 bool provided_{};
439};
440
441namespace detail {
442
443template <class T>
444struct ActionFor;
445
446template <>
447struct ActionFor<std::string> {
448 inline static constexpr auto set_once = conversion::string | pack::set_once;
449 inline static constexpr auto push = conversion::string | pack::push;
450 inline static constexpr auto write_to =
452};
453
454template <>
455struct ActionFor<bool> {
456 inline static constexpr auto set_once = conversion::boolean | pack::set_once;
457 inline static constexpr auto push = conversion::boolean | pack::push;
458 inline static constexpr auto write_to =
460};
461
462template <>
463struct ActionFor<std::filesystem::path> {
464 inline static constexpr auto set_once = conversion::path | pack::set_once;
465 inline static constexpr auto push = conversion::path | pack::push;
466 inline static constexpr auto write_to =
468};
469
470template <std::integral T>
471struct ActionFor<T> {
472 inline static constexpr auto set_once =
474 inline static constexpr auto push = conversion::integer<T> | pack::push;
475 inline static constexpr auto write_to =
477};
478
479template <std::floating_point T>
480struct ActionFor<T> {
481 inline static constexpr auto set_once =
483 inline static constexpr auto push = conversion::floating<T> | pack::push;
484 inline static constexpr auto write_to =
486};
487
488template <class T, Nargs N>
489struct PositionalActionFor {
490 inline static constexpr auto value = []() -> auto {
491 if constexpr (N.max == 1) {
492 return ActionFor<T>::set_once;
493 } else {
494 return ActionFor<T>::push;
495 }
496 }();
497};
498
499} // namespace detail
500
501// ── Core public API ───────────────────────────────────────────────────────────
502
525template <StringLiteral Name, auto... Args>
526using Arg = typename detail::ArgAlias<Name, Args...>::type;
527
536template <StringLiteral Name, char ShortName = '\0'>
538
552template <class T, StringLiteral Name, char ShortName = '\0'>
554
556template <StringLiteral Name, char ShortName = '\0'>
558
560template <StringLiteral Name, char ShortName = '\0'>
562
564template <StringLiteral Name, char ShortName = '\0'>
566
568template <StringLiteral Name, char ShortName = '\0'>
570
579template <StringLiteral Name = "help", char ShortName = 'h'>
580using Help =
582
593template <class T, StringLiteral Name, char ShortName = '\0'>
595
607template <class T, StringLiteral Name, char ShortName = '\0',
610
619template <class T, Nargs N = nargs::one>
621
622// ── Convenience aliases for common types ─────────────────────────────────────
623
625template <StringLiteral Name, char ShortName = '\0'>
627
629template <StringLiteral Name, char ShortName = '\0'>
631
633template <StringLiteral Name, char ShortName = '\0'>
635
637template <StringLiteral Name, char ShortName = '\0'>
639
640} // namespace cli
Concept that validates an aggregate type as a well-formed CLI argument specification.
Definition argument.hh:38
constexpr auto string
Definition action.hh:978
constexpr auto integer
Definition action.hh:921
constexpr auto floating
Definition action.hh:923
constexpr auto boolean
Definition action.hh:979
constexpr auto path
Definition action.hh:980
constexpr Nargs one_or_more
Accepts one or more values.
constexpr Nargs one
Accepts exactly one value.
constexpr auto push
Definition action.hh:1880
constexpr auto write_to
Definition action.hh:1888
constexpr auto set_once
Definition action.hh:1877
Definition action.hh:29
Arg< Name, ShortName, action::print_help|action::exit_success, nargs::none > Help
A built-in help flag that prints help and exits with code 0.
Definition argument.hh:580
PositionalImpl< N, detail::PositionalActionFor< T, N >::value > Positional
A typed positional argument.
Definition argument.hh:620
constexpr auto ok() -> ActionResult< void >
Helper function for constructing a successful ActionResult<void>.
Definition action.hh:205
constexpr auto optional
Convenience constant for Presence::optional.
Option< double, Name, ShortName > DoubleOption
Option<double, Name, ShortName>.
Definition argument.hh:634
Arg< Name, ShortName, detail::ActionFor< T >::push, N > ListOption
A multi-value option that appends each occurrence to a std::vector<T>.
Definition argument.hh:609
Arg< Name, ShortName, detail::ActionFor< T >::write_to > BoundOption
An option whose parsed value is written directly into an external variable.
Definition argument.hh:553
Option< std::string, Name, ShortName > StringOption
Option<std::string, Name, ShortName>.
Definition argument.hh:626
BoundOption< int, Name, ShortName > BoundIntOption
BoundOption specialisation for int.
Definition argument.hh:561
Option< int, Name, ShortName > IntOption
Option<int, Name, ShortName>.
Definition argument.hh:630
BoundOption< std::filesystem::path, Name, ShortName > BoundPathOption
BoundOption specialisation for std::filesystem::path.
Definition argument.hh:569
Option< std::filesystem::path, Name, ShortName > PathOption
Option<std::filesystem::path, Name, ShortName>.
Definition argument.hh:638
BoundOption< double, Name, ShortName > BoundDoubleOption
BoundOption specialisation for double.
Definition argument.hh:565
BoundOption< std::string, Name, ShortName > BoundStringOption
BoundOption specialisation for std::string.
Definition argument.hh:557
Arg< Name, ShortName, pack::set_true, nargs::none > Flag
A boolean flag option that stores true when present.
Definition argument.hh:537
typename detail::ArgAlias< Name, Args... >::type Arg
Defines a typed command-line argument specification.
Definition argument.hh:526
Presence
Controls whether an option or positional argument is mandatory.
Arg< Name, ShortName, detail::ActionFor< T >::set_once > Option
A single-value option that stores the parsed result in T.
Definition argument.hh:594
Context passed to each action function during pipeline execution.
Definition action.hh:246
The result type produced and consumed at each step of an action pipeline.
Definition action.hh:48
static constexpr auto ok(U &&v) -> ActionResult
Creates a success result holding value.
Definition action.hh:90
static constexpr auto fail(ParseError e) -> ActionResult
Creates a failure result carrying error.
Definition action.hh:100
Parameter bag used when constructing ArgImpl or PositionalImpl with named fields.
Definition argument.hh:66
std::function< void(const T &)> on_parse
Callback invoked once after parsing completes.
Definition argument.hh:72
std::string_view help
Help text shown in --help output.
Definition argument.hh:67
std::optional< T > default_value
Default value when the argument is absent.
Definition argument.hh:70
Presence presence
Whether the argument is required.
Definition argument.hh:68
std::string_view env
Environment variable name to fall back to when the option is absent from the command line.
Definition argument.hh:73
std::string_view deprecated
Optional deprecation message shown in generated help.
Definition argument.hh:77
bool hidden
Whether this argument is omitted from generated help.
Definition argument.hh:75
Parameter bag used when constructing a Command field.
Definition argument.hh:392
std::string_view help
Brief description shown in the parent command's help.
Definition argument.hh:394
std::string_view deprecated
Optional deprecation message shown in generated help.
Definition argument.hh:397
bool hidden
Whether this command is omitted from generated help.
Definition argument.hh:395
Tag base class for subcommand fields.
auto help_text() const -> std::string_view
Definition argument.hh:427
static constexpr auto command_name() -> std::string_view
Definition argument.hh:423
Command(CommandParameter param)
Definition argument.hh:418
auto deprecated() const -> std::string_view
Definition argument.hh:429
auto hidden() const -> bool
Definition argument.hh:428
auto mark_provided() -> void
Definition argument.hh:432
static constexpr auto name
Definition argument.hh:422
Command()=default
auto provided() const -> bool
Definition argument.hh:426
Tag base class for the Description field that provides the CLI's help text.
constexpr Description()=default
constexpr Description(const char(&s)[N])
Definition argument.hh:384
constexpr Description(std::string_view)=delete
std::string_view text
Definition argument.hh:380
Specifies the minimum and maximum number of values an option or positional accepts.
The main parsing engine for a typed argument specification.
Definition parser.hh:736
auto default_value() const -> const std::optional< value_type > &
Definition argument.hh:318
auto provided() const -> bool
Definition argument.hh:288
auto invoke_action(std::string_view text, std::size_t arg_index, std::size_t occurrence_nargs=1, std::size_t value_index=0) -> ActionResult< void >
Definition argument.hh:331
auto presence() const -> Presence
Definition argument.hh:316
typename std::remove_cvref_t< decltype(std::get< 1 >(A.validate()))>::type value_type
Definition argument.hh:271
auto operator*() const -> const value_type &
Definition argument.hh:294
auto help_text() const -> const std::string_view
Definition argument.hh:309
auto operator->() const -> const value_type *
Definition argument.hh:296
auto env() const -> std::string_view
Definition argument.hh:317
auto get_param() const -> const ArgParameter< value_type > &
Definition argument.hh:323
auto operator*() -> value_type &
Definition argument.hh:293
auto value() const -> const value_type &
Definition argument.hh:286
auto deprecated() const -> std::string_view
Definition argument.hh:313
auto on_parse() -> void
Definition argument.hh:282
auto value() -> value_type &
Definition argument.hh:287
auto hidden() const -> bool
Definition argument.hh:312
PositionalImpl(ArgParameter< value_type > param)
Definition argument.hh:275
auto occurrences() const -> std::size_t
Definition argument.hh:289
auto operator->() -> value_type *
Definition argument.hh:295
Tag base class for positional argument fields.
Compile-time string type usable as a non-type template parameter.