16namespace cli::detail {
19consteval auto members_are_derived_from_valid_cli_class() ->
bool {
20 return []<
class... Args>(std::type_identity<std::tuple<Args...>>)
consteval
22 return (std::derived_from<std::remove_cvref_t<Args>, SpecMemberTag> && ...);
24 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
28consteval auto options_have_unique_long_name() ->
bool {
29 std::vector<std::string_view> names;
30 [&]<
class... Args>(std::type_identity<std::tuple<Args...>>)
consteval
33 [&]()
consteval ->
auto {
34 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
36 names.push_back(std::remove_cvref_t<Args>::name.view());
41 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
42 std::ranges::sort(names);
43 return std::ranges::adjacent_find(names) == names.end();
47consteval auto options_have_unique_short_name() ->
bool {
48 std::vector<char> names;
49 [&]<
class... Args>(std::type_identity<std::tuple<Args...>>)
consteval
52 [&]()
consteval ->
auto {
53 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
55 if constexpr (std::remove_cvref_t<Args>::short_name !=
'\0') {
56 names.push_back(std::remove_cvref_t<Args>::short_name);
62 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
63 std::ranges::sort(names);
64 return std::ranges::adjacent_find(names) == names.end();
68consteval auto commands_have_unique_long_name() ->
bool {
69 std::vector<std::string_view> names;
70 [&]<
class... Args>(std::type_identity<std::tuple<Args...>>)
consteval
73 [&]()
consteval ->
auto {
74 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
76 names.push_back(std::remove_cvref_t<Args>::name.view());
81 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
82 std::ranges::sort(names);
83 return std::ranges::adjacent_find(names) == names.end();
87consteval auto positionals_have_variadic_at_end() {
88 bool found_variadic =
false;
89 bool found_positional_after_variadic =
false;
90 [&]<
class... Args>(std::type_identity<std::tuple<Args...>>)
consteval
93 [&]()
consteval ->
auto {
94 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
97 found_positional_after_variadic =
true;
99 if (std::remove_cvref_t<Args>::nargs.max !=
100 std::remove_cvref_t<Args>::nargs.min) {
101 found_variadic =
true;
106 }(std::type_identity<
107 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
108 return !found_positional_after_variadic;
112concept HasRelations =
requires {
113 { T::relations } -> RelationSetLike;
116template <
class Field>
117consteval auto relation_target_name() -> std::string_view {
118 using F = std::remove_cvref_t<Field>;
119 if constexpr (std::derived_from<F, OptionTag> ||
120 std::derived_from<F, CommandTag>) {
121 return F::name.view();
128consteval auto named_relation_targets() {
129 return [&]<
class... Args>(std::type_identity<std::tuple<Args...>>)
consteval
131 return std::array<std::string_view,
sizeof...(Args)>{
132 relation_target_name<Args>()...};
133 }(std::type_identity<
134 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
137template <
class Array>
138consteval auto contains_name(
const Array& names, std::string_view name) ->
bool {
139 for (
auto candidate : names) {
140 if (!candidate.empty() && candidate == name)
return true;
145template <
class T, std::
size_t Limit, std::
size_t I = 0>
146consteval auto group_name_exists_before(std::string_view name) ->
bool {
147 if constexpr (I >= Limit) {
150 constexpr auto rel = std::get<I>(T::relations.items);
151 using R = std::remove_cvref_t<
decltype(rel)>;
152 if constexpr (std::same_as<R, GroupRelation>) {
153 if (rel.name == name)
return true;
155 return group_name_exists_before<T, Limit, I + 1>(name);
159template <
class T, std::
size_t I = 0>
160consteval auto group_name_exists(std::string_view name) ->
bool {
161 constexpr auto size =
162 std::tuple_size_v<std::remove_cvref_t<
decltype(T::relations.items)>>;
163 return group_name_exists_before<T, size, I>(name);
167consteval auto operand_exists(std::string_view name) ->
bool {
168 constexpr auto field_names = named_relation_targets<T>();
169 return contains_name(field_names, name) || group_name_exists<T>(name);
172template <
class T, std::
size_t I = 0>
173consteval auto relation_membership_count(std::string_view name) -> std::size_t {
174 constexpr auto size =
175 std::tuple_size_v<std::remove_cvref_t<
decltype(T::relations.items)>>;
176 if constexpr (I >= size) {
179 constexpr auto rel = std::get<I>(T::relations.items);
180 using R = std::remove_cvref_t<
decltype(rel)>;
181 std::size_t current = 0;
182 if constexpr (std::same_as<R, GroupRelation>) {
183 for (
auto member : rel.members) {
184 if (member == name) ++current;
186 }
else if constexpr (std::same_as<R, ConflictsRelation>) {
187 if (rel.left == name) ++current;
188 if (rel.right == name) ++current;
189 }
else if constexpr (std::same_as<R, DependsOnRelation>) {
190 if (rel.source == name) ++current;
191 if (rel.target == name) ++current;
193 return current + relation_membership_count<T, I + 1>(name);
198consteval auto field_relation_memberships_are_unique() ->
bool {
199 constexpr auto field_names = named_relation_targets<T>();
200 for (
auto name : field_names) {
201 if (!name.empty() && relation_membership_count<T>(name) > 1) {
208template <
class T, std::
size_t I = 0>
209consteval auto relation_items_are_well_formed() ->
bool {
210 constexpr auto size =
211 std::tuple_size_v<std::remove_cvref_t<
decltype(T::relations.items)>>;
212 if constexpr (I >= size) {
215 constexpr auto rel = std::get<I>(T::relations.items);
216 using R = std::remove_cvref_t<
decltype(rel)>;
217 constexpr auto field_names = named_relation_targets<T>();
218 if constexpr (std::same_as<R, GroupRelation>) {
219 if (rel.name.empty() || rel.members.size < 2 ||
220 contains_name(field_names, rel.name) ||
221 group_name_exists_before<T, I>(rel.name)) {
224 for (
auto member : rel.members) {
225 if (!contains_name(field_names, member))
return false;
227 }
else if constexpr (std::same_as<R, ConflictsRelation>) {
228 if (rel.left.empty() || rel.right.empty() || rel.left == rel.right ||
229 !operand_exists<T>(rel.left) || !operand_exists<T>(rel.right)) {
232 }
else if constexpr (std::same_as<R, DependsOnRelation>) {
233 if (rel.source.empty() || rel.target.empty() || rel.source == rel.target ||
234 !operand_exists<T>(rel.source) || !operand_exists<T>(rel.target)) {
240 return relation_items_are_well_formed<T, I + 1>();
245consteval auto relations_are_well_formed() ->
bool {
246 if constexpr (!HasRelations<T>) {
249 return relation_items_are_well_formed<T>() &&
250 field_relation_memberships_are_unique<T>();
254template <StringLiteral Name>
256constexpr auto is_valid_long_option_name() noexcept ->
bool {
257 constexpr auto size = Name.size();
259 if constexpr (size == 0) {
262 auto is_alpha = [](
char c)
constexpr ->
bool {
263 return (
'a' <= c && c <=
'z');
265 auto is_digit = [](
char c)
constexpr ->
bool {
return '0' <= c && c <=
'9'; };
266 auto is_alnum = [&](
char c)
constexpr ->
bool {
267 return is_alpha(c) || is_digit(c);
270 if (!is_alpha(Name[0])) {
273 bool previous_is_hyphen =
false;
274 for (std::size_t i = 1; i < size; ++i) {
275 const char c = Name[i];
277 if (previous_is_hyphen) {
280 previous_is_hyphen =
true;
286 previous_is_hyphen =
false;
289 return !previous_is_hyphen;
292constexpr auto is_valid_short_option_name(
char Name)
noexcept ->
bool {
293 return (
'a' <= Name && Name <=
'z') || (
'A' <= Name && Name <=
'Z') ||
297template <StringLiteral Name>
299consteval auto is_valid_command_name() noexcept {
300 return is_valid_long_option_name<Name>();
303template <Nargs NargsValue>
305consteval auto is_valid_nargs() noexcept ->
bool {
306 if (NargsValue.min < 0) {
309 if (NargsValue.max != -1 && NargsValue.max < NargsValue.min) {