cli20 0.1.2
A C++20 typed-schema command-line parser
Loading...
Searching...
No Matches
argument_validation.hh
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <concepts>
6#include <string_view>
7#include <tuple>
8#include <type_traits>
9#include <vector>
10
11#include "cli/argument_base.hh"
12#include "cli/meta.hh"
13#include "cli/relation.hh"
14#include "cli/string_literal.hh"
15
16namespace cli::detail {
17
18template <class T>
19consteval auto members_are_derived_from_valid_cli_class() -> bool {
20 return []<class... Args>(std::type_identity<std::tuple<Args...>>) consteval
21 -> auto {
22 return (std::derived_from<std::remove_cvref_t<Args>, SpecMemberTag> && ...);
23 }(std::type_identity<
24 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
25}
26
27template <class 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
31 -> auto {
32 (
33 [&]() consteval -> auto {
34 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
35 OptionTag>) {
36 names.push_back(std::remove_cvref_t<Args>::name.view());
37 }
38 }(),
39 ...);
40 }(std::type_identity<
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();
44}
45
46template <class T>
47consteval auto options_have_unique_short_name() -> bool {
48 std::vector<char> names;
49 [&]<class... Args>(std::type_identity<std::tuple<Args...>>) consteval
50 -> auto {
51 (
52 [&]() consteval -> auto {
53 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
54 OptionTag>) {
55 if constexpr (std::remove_cvref_t<Args>::short_name != '\0') {
56 names.push_back(std::remove_cvref_t<Args>::short_name);
57 }
58 }
59 }(),
60 ...);
61 }(std::type_identity<
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();
65}
66
67template <class T>
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
71 -> auto {
72 (
73 [&]() consteval -> auto {
74 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
75 CommandTag>) {
76 names.push_back(std::remove_cvref_t<Args>::name.view());
77 }
78 }(),
79 ...);
80 }(std::type_identity<
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();
84}
85
86template <class T>
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
91 -> auto {
92 (
93 [&]() consteval -> auto {
94 if constexpr (std::derived_from<std::remove_cvref_t<Args>,
95 PositionalTag>) {
96 if (found_variadic) {
97 found_positional_after_variadic = true;
98 }
99 if (std::remove_cvref_t<Args>::nargs.max !=
100 std::remove_cvref_t<Args>::nargs.min) {
101 found_variadic = true;
102 }
103 }
104 }(),
105 ...);
106 }(std::type_identity<
107 std::remove_cvref_t<decltype(as_tuple(std::declval<T>()))>>{});
108 return !found_positional_after_variadic;
109}
110
111template <class T>
112concept HasRelations = requires {
113 { T::relations } -> RelationSetLike;
114};
115
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();
122 } else {
123 return {};
124 }
125}
126
127template <class T>
128consteval auto named_relation_targets() {
129 return [&]<class... Args>(std::type_identity<std::tuple<Args...>>) consteval
130 -> auto {
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>()))>>{});
135}
136
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;
141 }
142 return false;
143}
144
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) {
148 return false;
149 } else {
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;
154 }
155 return group_name_exists_before<T, Limit, I + 1>(name);
156 }
157}
158
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);
164}
165
166template <class T>
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);
170}
171
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) {
177 return 0;
178 } else {
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;
185 }
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;
192 }
193 return current + relation_membership_count<T, I + 1>(name);
194 }
195}
196
197template <class T>
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) {
202 return false;
203 }
204 }
205 return true;
206}
207
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) {
213 return true;
214 } else {
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)) {
222 return false;
223 }
224 for (auto member : rel.members) {
225 if (!contains_name(field_names, member)) return false;
226 }
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)) {
230 return false;
231 }
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)) {
235 return false;
236 }
237 } else {
238 return false;
239 }
240 return relation_items_are_well_formed<T, I + 1>();
241 }
242}
243
244template <class T>
245consteval auto relations_are_well_formed() -> bool {
246 if constexpr (!HasRelations<T>) {
247 return true;
248 } else {
249 return relation_items_are_well_formed<T>() &&
250 field_relation_memberships_are_unique<T>();
251 }
252}
253
254template <StringLiteral Name>
255[[nodiscard]]
256constexpr auto is_valid_long_option_name() noexcept -> bool {
257 constexpr auto size = Name.size();
258
259 if constexpr (size == 0) {
260 return false;
261 }
262 auto is_alpha = [](char c) constexpr -> bool {
263 return ('a' <= c && c <= 'z');
264 };
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);
268 };
269
270 if (!is_alpha(Name[0])) {
271 return false;
272 }
273 bool previous_is_hyphen = false;
274 for (std::size_t i = 1; i < size; ++i) {
275 const char c = Name[i];
276 if (c == '-') {
277 if (previous_is_hyphen) {
278 return false;
279 }
280 previous_is_hyphen = true;
281 continue;
282 }
283 if (!is_alnum(c)) {
284 return false;
285 }
286 previous_is_hyphen = false;
287 }
288
289 return !previous_is_hyphen;
290}
291
292constexpr auto is_valid_short_option_name(char Name) noexcept -> bool {
293 return ('a' <= Name && Name <= 'z') || ('A' <= Name && Name <= 'Z') ||
294 Name == '\0';
295}
296
297template <StringLiteral Name>
298[[nodiscard]]
299consteval auto is_valid_command_name() noexcept {
300 return is_valid_long_option_name<Name>();
301}
302
303template <Nargs NargsValue>
304[[nodiscard]]
305consteval auto is_valid_nargs() noexcept -> bool {
306 if (NargsValue.min < 0) {
307 return false;
308 }
309 if (NargsValue.max != -1 && NargsValue.max < NargsValue.min) {
310 return false;
311 }
312 return true;
313}
314
315} // namespace cli::detail