C++ Essence Library 0.1.0
A Utility Library for Modern C++ Programming
Loading...
Searching...
No Matches
option.hpp
1/*
2 * Copyright (c) 2024 The RefValue Project
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#pragma once
24
25#include "../abi/string.hpp"
26#include "../abi/vector.hpp"
27#include "../basic_string.hpp"
28#include "../char8_t_remediation.hpp"
29#include "../compat.hpp"
30#include "../meta/fingerprint.hpp"
31#include "../meta/friendly_name_vector.hpp"
32#include "../meta/runtime/boolean.hpp"
33#include "../meta/runtime/enum.hpp"
34#include "../numeric_conversion.hpp"
35#include "../range.hpp"
36#include "../string.hpp"
37#include "abstract/option.hpp"
38#include "common_types.hpp"
39
40#include <algorithm>
41#include <concepts>
42#include <functional>
43#include <optional>
44#include <ranges>
45#include <span>
46#include <string_view>
47#include <type_traits>
48#include <unordered_set>
49#include <utility>
50
51namespace essence::cli {
56 ES_API(CPPESSENCE) abstract::option make_base_option();
57
62 template <std::default_initializable T>
63 requires((std::is_arithmetic_v<range_value_t_or_self_t<T>> || std::is_enum_v<range_value_t_or_self_t<T>>
64 || std_basic_string<range_value_t_or_self_t<T>>)
65 && (std::same_as<range_value_t_or_self_t<T>, T> ? true : extendable_contiguous_range<T>) )
66 class option {
67 public:
68 using element_type = std::conditional_t<std_basic_string<T>, T, range_value_t_or_self_t<T>>;
69 using set_hash_type = std::conditional_t<std_basic_string<element_type>, string_hash, std::hash<element_type>>;
70
71 static constexpr std::string_view delimiter{U8(",")};
72 static constexpr std::string_view optional_pattern{U8("[]")};
73 static constexpr std::string_view keyword_pattern{U8("``")};
74 static constexpr meta::fingerprint type_id{std::type_identity<T>{}};
75
79 option() : base_{make_base_option()} {
80 // Makes default values for an enumeration.
81 if constexpr (std::is_enum_v<element_type>) {
82 for (auto&& [name, value] : meta::runtime::get_enum_names<element_type>(true)) {
83 valid_value_strs_.emplace_back(name);
84 valid_values_.emplace(value);
85 }
86 } else if constexpr (std::same_as<T, bool>) {
87 valid_value_strs_.emplace_back(meta::true_string);
88 valid_value_strs_.emplace_back(meta::false_string);
89 valid_values_.emplace(true);
90 valid_values_.emplace(false);
91 set_default_value(false);
92 }
93 }
94
95 [[nodiscard]] abi::string bound_name() const {
96 return base_.bound_name();
97 }
98
99 option& set_bound_name(std::string_view name) {
100 static_cast<void>(base_.set_bound_name(name));
101
102 return *this;
103 }
104
105 [[nodiscard]] abi::string description() const {
106 return base_.description();
107 }
108
109 option& set_description(std::string_view description) {
110 static_cast<void>(base_.set_description(description));
111
112 return *this;
113 }
114
115 [[nodiscard]] std::span<const abi::string> aliases() const {
116 return base_.aliases();
117 }
118
119 option& add_aliases(std::span<const abi::string> aliases) {
120 static_cast<void>(base_.add_aliases(aliases));
121
122 return *this;
123 }
124
125 [[nodiscard]] std::optional<abi::string> default_value_str() const {
126 return default_value_str_;
127 }
128
129 [[nodiscard]] std::span<const abi::string> valid_value_strs() const noexcept {
130 return valid_value_strs_;
131 }
132
133 [[nodiscard]] abi::string name_hints() const {
134 auto hints = base_.name_hints();
135
136 // An optional option, surrounded by [].
137 if (default_value_str()) {
138 hints.insert(hints.begin(), optional_pattern.front());
139 hints.push_back(optional_pattern.back());
140 }
141
142 return hints;
143 }
144
145 [[nodiscard]] abi::string value_hints() const {
146 abi::string hints;
147
148 if (valid_value_strs().empty()) {
149 hints.push_back(keyword_pattern.front());
150 hints.append(type_id.friendly_name());
151 hints.push_back(keyword_pattern.back());
152 } else {
153 auto joint = join_with(valid_value_strs_, delimiter);
154
155 hints.assign(joint.begin(), joint.end());
156 }
157
158 return hints;
159 }
160
161 [[nodiscard]] static bool check_target_type(meta::fingerprint id) noexcept {
162 return id == type_id;
163 }
164
165 [[nodiscard]] bool parse_value_and_cache(std::string_view value) {
166 thread_local bool success = true;
167
168 if constexpr (!std_basic_string<T> && extendable_contiguous_range<T>) {
169 parse_range_value_and_cache(value, success);
170 } else {
171 auto parsed_value = from_element_string(value);
172
173 if ((success = parsed_value.has_value())) {
174 validate_range(*parsed_value, success);
175 }
176
177 if (!success) {
178 raise_error(U8("Invalid value."));
179
180 return success;
181 }
182
183 parsed_value.swap(cached_value_);
184 }
185
186 // Invokes the consumer's validator.
187 if (success) {
188 if (validation_result result; !(validate(value, result), result.success)) {
189 success = result.success;
190
191 raise_error(result.error);
192 }
193 }
194
195 return success;
196 }
197
198 void set_target_from_cache(void* target) {
199 if (target && (cached_value_ || default_value_)) {
200 auto value = cached_value_ ? *cached_value_ : *default_value_;
201
202 // Selects std::swap or T::swap instead for higher performance if exists.
203 if constexpr (requires(T obj) { std::swap(obj, obj); }) {
204 std::swap(*static_cast<T*>(target), value);
205 } else if constexpr (requires(T obj) { T{}.swap(obj); }) {
206 value.swap(*static_cast<T*>(target));
207 } else {
208 *static_cast<T*>(target) = value;
209 }
210 }
211 }
212
213 void validate(std::string_view value, validation_result& result) const {
214 base_.validate(value, result);
215 }
216
217 void raise_error(std::string_view message) const {
218 base_.raise_error(message);
219 }
220
221 void on_validation(const validation_handler& handler) const {
222 base_.on_validation(handler);
223 }
224
225 void on_error(const output_handler& handler) const {
226 base_.on_error(handler);
227 }
228
229 template <std::convertible_to<std::string_view>... Args>
230 option& add_aliases(Args&&... args) {
231 add_aliases(std::array{abi::string{std::string_view{std::forward<Args>(args)}}...});
232
233 return *this;
234 }
235
236 template <typename U>
237 requires std::constructible_from<T, U>
238 option& set_default_value(U&& value) {
239 default_value_.emplace(std::forward<U>(value));
240
241 // If T is a range, creates a comma-separated string with its elements.
242 if constexpr (!std_basic_string<T> && extendable_contiguous_range<T>) {
243 auto strs = (*default_value_) | std::views::transform(&option::to_element_string);
244 auto joint = join_with(strs, delimiter);
245
246 default_value_str_.emplace(joint.begin(), joint.end());
247 } else {
248 default_value_str_.emplace(to_element_string(*default_value_));
249 }
250
251 return *this;
252 }
253
254 option& set_valid_values(std::span<const element_type> values) {
255 decltype(valid_values_){values.begin(), values.end()}.swap(valid_values_);
256
257 auto adapter = valid_values_ | std::views::transform(&option::to_element_string);
258
259 valid_value_strs_.assign(adapter.begin(), adapter.end());
260
261 return *this;
262 }
263
264 template <typename... Args>
265 requires(std::constructible_from<element_type, Args> && ...)
266 option& set_valid_values(Args&&... args) {
267 set_valid_values(std::array{element_type{std::forward<Args>(args)}...});
268
269 return *this;
270 }
271
272 abstract::option as_abstract() {
273 return abstract::option{std::move(*this)};
274 }
275
276 private:
277 static abi::string to_element_string(const element_type& item) {
278 if constexpr (std::convertible_to<element_type, std::string_view>) {
279 return abi::string{std::string_view{item}};
280 } else if constexpr (std::is_enum_v<element_type> || std::same_as<element_type, bool>) {
281 return abi::to_abi_string(meta::runtime::to_string(item));
282 } else if constexpr (std::is_arithmetic_v<element_type>) {
283 return abi::to_abi_string(to_string(item));
284 } else {
285 return {};
286 }
287 }
288
289 static std::optional<element_type> from_element_string(std::string_view str) {
290 if constexpr (std_basic_string<element_type>) {
291 return element_type{str};
292 } else if constexpr (std::is_enum_v<element_type>) {
293 return meta::runtime::from_string<element_type>(str);
294 } else if constexpr (std::same_as<element_type, bool>) {
295 return meta::runtime::from_string<bool>(str);
296 } else if constexpr (std::is_arithmetic_v<element_type>) {
297 return from_string<element_type>(str);
298 } else {
299 return std::nullopt;
300 }
301 }
302
303 void validate_range(const element_type& value, bool& success) const {
304 if (success = valid_values_.empty() ? true : valid_values_.contains(value); !success) {
305 auto joint = join_with(valid_value_strs_, delimiter);
306
307 raise_error(U8("The value was out of range."));
308 raise_error(U8("One of the following values is allowed:"));
309 raise_error(abi::string{joint.begin(), joint.end()});
310 }
311 }
312
313 void parse_range_value_and_cache(std::string_view value, bool& success) {
314 auto validator = [&](const auto& inner) {
315 if ((success = inner.has_value())) {
316 validate_range(*inner, success);
317 } else {
318 raise_error(U8("Invalid value."));
319 }
320
321 return success;
322 };
323
324 if constexpr (!std_basic_string<T> && extendable_contiguous_range<T>) {
325 auto parts = std::views::split(value, delimiter) | std::views::transform([](const auto& inner) {
326 // The return type of split is implementation-defined, so we should use
327 // the common adapter to convert it to a normal sub-range.
328 auto iter = inner | std::views::common;
329
330 return from_element_string(abi::string{iter.begin(), iter.end()});
331 }) | std::views::take_while(validator)
332 | std::views::transform([](const auto& inner) { return *inner; }) | std::views::common;
333
334 if (success) {
335 cached_value_.emplace();
336
337 // The extendable_contiguous_range concept ensures that
338 // .clear(), .push_back() and .shrink_to_fit exist.
339 std::ranges::copy(parts, std::back_inserter(*cached_value_));
340 cached_value_->shrink_to_fit();
341 } else {
342 cached_value_.reset();
343 }
344 }
345 }
346
347 abstract::option base_;
348 std::optional<T> cached_value_;
349 std::optional<T> default_value_;
350 std::optional<abi::string> default_value_str_;
351 abi::vector<abi::string> valid_value_strs_;
352 std::unordered_set<element_type, set_hash_type, std::equal_to<>> valid_values_;
353 };
354} // namespace essence::cli
Creates a base internal implementation of a CLI option class.
Definition option.hpp:66
option()
Creates an instance.
Definition option.hpp:79
A unique identifier of a type, i.e. a fingerprint.
Definition fingerprint.hpp:34
@ value
the parser finished reading a JSON value
A hash function for const char*, std::string_view and std::string.
Definition string.hpp:43