cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
click_impl.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <optional> // Add optional include
5#include <sstream> // For std::stringstream
6#include <stdexcept> // For std::invalid_argument, std::runtime_error
7#include <string>
8#include <type_traits> // For std::is_integral, std::is_floating_point
9
13
15{
16
17} // namespace toolbox::utils::impl
18
19namespace toolbox::utils
20{
21namespace detail
22{
23// Type trait to check if T is std::optional<U>
24template<typename T>
25struct is_optional : std::false_type
26{
27};
28template<typename U>
29struct is_optional<std::optional<U>> : std::true_type
30{
31};
32template<typename T>
33inline constexpr bool is_optional_v =
35
36// Helper to get the value_type of std::optional
37template<typename T>
39{
40};
41template<typename U>
42struct optional_value_type<std::optional<U>>
43{
44 using type = U;
45};
46template<typename T>
48 std::remove_cv_t<std::remove_reference_t<T>>>::type;
49
50// Type trait to check if a type has stream extraction operator (>>)
51template<typename T, typename = void>
52struct has_istream_operator : std::false_type
53{
54};
55
56template<typename T>
58 T,
59 std::void_t<decltype(std::declval<std::istream&>() >> std::declval<T&>())>>
60 : std::is_base_of<
61 std::istream,
62 std::remove_reference_t<decltype(std::declval<std::istream&>()
63 >> std::declval<T&>())>>
64{
65};
66// Checks if the result of operator>> is actually a stream reference
67
68template<typename T>
69inline constexpr bool has_istream_operator_v =
71
72} // namespace detail
73
74// Helper function template to parse the underlying value for std::optional
75// Moved to toolbox::utils namespace to potentially help MSVC name lookup
76template<typename T_Optional> // T_Optional is std::optional<U>
77bool parse_optional_value(const std::string& input, T_Optional& output)
78{
79 // This function is only instantiated when T_Optional is std::optional,
80 // so using detail::optional_value_type_t here is safe.
81 using optional_value_type = detail::optional_value_type_t<T_Optional>;
82 optional_value_type parsed_value;
83 std::stringstream ss(input);
84 bool parse_success = false;
85
86 if constexpr (std::is_same_v<optional_value_type, bool>) {
87 std::string lower_input;
88 std::transform(
89 input.begin(), input.end(), std::back_inserter(lower_input), ::tolower);
90 if (lower_input == "true" || lower_input == "1") {
91 parsed_value = true;
92 parse_success = true;
93 } else if (lower_input == "false" || lower_input == "0") {
94 parsed_value = false;
95 parse_success = true;
96 } else {
97 parse_success = false;
98 }
99 } else if constexpr (std::is_integral_v<optional_value_type>) {
100 if (input.size() > 2 && input[0] == '0'
101 && (input[1] == 'x' || input[1] == 'X'))
102 {
103 ss >> std::hex >> parsed_value;
104 } else {
105 ss >> parsed_value;
106 }
107 parse_success = (ss.eof() && !ss.fail());
108 } else if constexpr (std::is_floating_point_v<optional_value_type>) {
109 ss >> parsed_value;
110 parse_success = (ss.eof() && !ss.fail());
111 } else if constexpr (std::is_same_v<optional_value_type, std::string>) {
112 parsed_value = input;
113 parse_success = true;
114 } else {
115 if constexpr (detail::has_istream_operator_v<optional_value_type>) {
116 ss >> parsed_value;
117 parse_success = (ss.eof() && !ss.fail());
118 } else {
119 LOG_WARN_S << "Unsupported type for default optional parser: "
120 << typeid(optional_value_type).name();
121 parse_success = false;
122 }
123 }
124
125 if (parse_success) {
126 output = parsed_value;
127 } else {
128 output = std::nullopt;
129 }
130 return parse_success;
131}
132
133// option_t Implementation
134template<typename T>
135option_t<T>::option_t(const std::string& name,
136 const std::string& short_name,
137 const std::string& description,
138 bool required)
139 : parameter_t(
140 name, description, detail::is_optional_v<T> ? false : required)
141 , short_name_(short_name)
142{
143 set_default_parser(); // Setup default parser based on type T
144 if constexpr (detail::is_optional_v<T>) {
145 value_ = std::nullopt;
146 default_value_ = std::nullopt;
147 }
148}
149
150template<typename T>
151option_t<T>& option_t<T>::set_default(const T& default_value)
152{
153 default_value_ = default_value;
154 value_ = default_value; // Initialize value with default
155 return *this;
156}
157
158template<typename T>
160 std::function<bool(const std::string&, T&)> parser)
161{
162 parser_ = std::move(parser);
163 return *this;
164}
165
166template<typename T>
167[[nodiscard]] std::string option_t<T>::get_short_name() const
168{
169 return short_name_;
170}
171
172template<typename T>
174{
175 if constexpr (detail::is_optional_v<T>) {
176 return value_;
177 } else {
178 return is_set_ ? value_ : default_value_;
179 }
180}
181
182template<typename T>
183bool option_t<T>::parse(const std::string& value)
184{
185 if (!parser_) {
186 LOG_CRITICAL_S << "Internal Error: Parser function not set for option '"
187 << name_ << "'.";
188 throw std::runtime_error("Parser function not set for option: " + name_);
189 }
190
191 if constexpr (detail::is_optional_v<T>) {
192 is_set_ = true; // Mark as set even if value is empty or parsing fails
193 // (resulting in nullopt)
194 // Call the parser to potentially set the value (or nullopt on failure)
195 /* bool underlying_parse_success = */ parser_(value, value_);
196 // For optional options, always return true. Failure to parse the underlying
197 // value results in nullopt, which is a valid state for an optional.
198 return true;
199 } else {
200 T parsed_value;
201 if (parser_(value, parsed_value)) {
202 value_ = parsed_value;
203 is_set_ = true;
204 return true;
205 } else {
206 return false;
207 }
208 }
209}
210
211template<typename T>
213{
214 if constexpr (detail::is_optional_v<T>) {
215 // Lambda specifically for std::optional types
216 parser_ = [](const std::string& input, T& output) -> bool
217 {
218 if (input.empty()) {
219 output = std::nullopt;
220 return true; // Successfully handled empty input
221 }
222 // Delegate actual value parsing to the helper function
223 return parse_optional_value(input, output);
224 };
225 } else {
226 // Lambda specifically for non-optional types
227 parser_ = [](const std::string& input, T& output) -> bool
228 {
229 std::stringstream ss(input);
230 if constexpr (std::is_same_v<T, bool>) {
231 std::string lower_input;
232 std::transform(input.begin(),
233 input.end(),
234 std::back_inserter(lower_input),
235 ::tolower);
236 if (lower_input == "true" || lower_input == "1") {
237 output = true;
238 return true;
239 } else if (lower_input == "false" || lower_input == "0") {
240 output = false;
241 return true;
242 } else {
243 return false; // Invalid boolean string
244 }
245 } else if constexpr (std::is_integral_v<T>) {
246 if (input.size() > 2 && input[0] == '0'
247 && (input[1] == 'x' || input[1] == 'X'))
248 {
249 ss >> std::hex >> output;
250 } else {
251 ss >> output;
252 }
253 } else if constexpr (std::is_floating_point_v<T>) {
254 ss >> output;
255 } else if constexpr (std::is_same_v<T, std::string>) {
256 output = input;
257 return true;
258 } else {
259 if constexpr (detail::has_istream_operator_v<T>) {
260 ss >> output;
261 } else {
262 LOG_WARN_S << "Unsupported type for default parser (non-optional): "
263 << typeid(T).name();
264 return false;
265 }
266 }
267 return ss.eof() && !ss.fail();
268 };
269 }
270}
271
272template<typename T>
274{
275 return detail::is_optional_v<T>;
276}
277
278template<typename T>
280{
281 return true;
282}
283
284// argument_t Implementation
285template<typename T>
286argument_t<T>::argument_t(const std::string& name,
287 const std::string& description,
288 bool required)
289 : parameter_t(
290 name, description, detail::is_optional_v<T> ? false : required)
291{
292 set_default_parser(); // Setup default parser based on type T
293 if constexpr (detail::is_optional_v<T>) {
294 value_ = std::nullopt;
295 default_value_ = std::nullopt;
296 }
297}
298
299template<typename T>
301{
302 default_value_ = default_value;
303 value_ = default_value; // Initialize value with default
304 return *this;
305}
306
307template<typename T>
309{
310 if constexpr (detail::is_optional_v<T>) {
311 return value_;
312 } else {
313 return is_set_ ? value_ : default_value_;
314 }
315}
316
317template<typename T>
318bool argument_t<T>::parse(const std::string& value)
319{
320 if (!parser_) {
321 LOG_CRITICAL_S << "Internal Error: Parser function not set for argument '"
322 << name_ << "'.";
323 throw std::runtime_error("Parser function not set for argument: " + name_);
324 }
325
326 if (value.empty()) {
327 // Generally treat empty as parse failure for arguments, let parser handle
328 // details.
329 }
330
331 if constexpr (detail::is_optional_v<T>) {
332 // Call the parser to potentially set the value (or nullopt on failure)
333 /* bool underlying_parse_success = */ parser_(value, value_);
334 // Mark optional arguments as set even if parsing failed (resulting in
335 // nullopt)
336 is_set_ = true;
337 // For optional arguments, always return true. Failure to parse the
338 // underlying value results in nullopt, which is a valid state for an
339 // optional argument.
340 return true;
341 } else {
342 T parsed_value;
343 bool parse_success = parser_(value, parsed_value);
344 if (parse_success) {
345 value_ = parsed_value;
346 is_set_ = true;
347 }
348 return parse_success; // Return the success status of the underlying parse
349 // operation.
350 }
351}
352
353template<typename T>
355{
356 if constexpr (detail::is_optional_v<T>) {
357 // Lambda specifically for std::optional arguments
358 parser_ = [](const std::string& input, T& output) -> bool
359 {
360 // Delegate actual value parsing to the helper function (no detail::
361 // needed)
362 return parse_optional_value(input, output);
363 };
364 } else {
365 // Lambda specifically for non-optional arguments
366 parser_ = [](const std::string& input, T& output) -> bool
367 {
368 std::stringstream ss(input);
369 if constexpr (std::is_same_v<T, bool>) {
370 std::string lower_input;
371 std::transform(input.begin(),
372 input.end(),
373 std::back_inserter(lower_input),
374 ::tolower);
375 if (lower_input == "true" || lower_input == "1") {
376 output = true;
377 return true;
378 } else if (lower_input == "false" || lower_input == "0") {
379 output = false;
380 return true;
381 } else {
382 return false; // Invalid boolean string
383 }
384 } else if constexpr (std::is_integral_v<T>) {
385 if (input.size() > 2 && input[0] == '0'
386 && (input[1] == 'x' || input[1] == 'X'))
387 {
388 ss >> std::hex >> output;
389 } else {
390 ss >> output;
391 }
392 } else if constexpr (std::is_floating_point_v<T>) {
393 ss >> output;
394 } else if constexpr (std::is_same_v<T, std::string>) {
395 output = input;
396 return true;
397 } else {
398 if constexpr (detail::has_istream_operator_v<T>) {
399 ss >> output;
400 } else {
402 << "Unsupported type for default parser (non-optional argument): "
403 << typeid(T).name();
404 return false;
405 }
406 }
407 return ss.eof() && !ss.fail();
408 };
409 }
410}
411
412template<typename T>
414{
415 return false;
416}
417
418template<typename T>
420{
421 return true;
422}
423
424template<typename T>
426{
427 return "";
428}
429
430// command_t Template Method Implementations
431template<typename T>
432option_t<T>& command_t::add_option(const std::string& name,
433 const std::string& short_name,
434 const std::string& description,
435 bool required /* = false */)
436{
437 if (name.rfind("--", 0) == 0) {
438 throw std::invalid_argument("Option name should not start with '--': "
439 + name);
440 }
441 if (!short_name.empty() && short_name.rfind("-", 0) == 0) {
442 throw std::invalid_argument("Option short name should not start with '-': "
443 + short_name);
444 }
445 if (!short_name.empty() && short_name.length() > 1) {
446 throw std::invalid_argument("Option short name must be a single character: "
447 + short_name);
448 }
449
450 auto option =
451 std::make_unique<option_t<T>>(name, short_name, description, required);
452 option_t<T>* option_ptr = option.get();
453 parameters_.push_back(std::move(option));
454 return *option_ptr;
455}
456
457template<typename T>
458argument_t<T>& command_t::add_argument(const std::string& name,
459 const std::string& description,
460 bool required)
461{
462 auto argument = std::make_unique<argument_t<T>>(name, description, required);
463 argument_t<T>* argument_ptr = argument.get();
464 parameters_.push_back(std::move(argument));
465 return *argument_ptr;
466}
467
469 const std::string& section)
470{
471 std::string current_section = section.empty() ? name_ : section;
472 for (const auto& param_ptr : parameters_) {
473 const std::string key = param_ptr->get_name();
474 if (config.has(current_section, key)) {
475 param_ptr->parse(config.get_string(current_section, key));
476 }
477 }
478
479 for (const auto& cmd : subcommands_) {
480 cmd->apply_ini_config(config, current_section + "." + cmd->get_name());
481 }
482}
483
484inline void command_t::apply_ini_file(const std::string& file_path,
485 const std::string& section)
486{
487 ini_config_t cfg;
488 if (cfg.load(file_path))
489 apply_ini_config(cfg, section);
490}
491
492} // namespace toolbox::utils
Definition click.hpp:85
bool parse(const std::string &value) override
Definition click_impl.hpp:318
bool accepts_missing_value() const override
Definition click_impl.hpp:413
argument_t(const std::string &name, const std::string &description, bool required=true)
Definition click_impl.hpp:286
bool is_argument() const override
Definition click_impl.hpp:419
T get() const
Definition click_impl.hpp:308
argument_t & set_default(const T &default_value)
Definition click_impl.hpp:300
std::string get_short_name() const override
Definition click_impl.hpp:425
void apply_ini_file(const std::string &file_path, const std::string &section="")
Definition click_impl.hpp:484
argument_t< T > & add_argument(const std::string &name, const std::string &description, bool required=true)
Definition click_impl.hpp:458
void apply_ini_config(const class ini_config_t &config, const std::string &section="")
Definition click_impl.hpp:468
option_t< T > & add_option(const std::string &name, const std::string &short_name, const std::string &description, bool required=false)
Definition click_impl.hpp:432
Definition ini_config.hpp:11
bool load(const std::filesystem::path &file_path)
bool has(const std::string &section, const std::string &key) const
std::string get_string(const std::string &section, const std::string &key, const std::string &default_value="") const
Definition click.hpp:44
bool parse(const std::string &value) override
Definition click_impl.hpp:183
option_t & set_default(const T &default_value)
Definition click_impl.hpp:151
option_t & set_parser(std::function< bool(const std::string &, T &)> parser)
Definition click_impl.hpp:159
std::string get_short_name() const override
Definition click_impl.hpp:167
option_t(const std::string &name, const std::string &short_name, const std::string &description, bool required=false)
Definition click_impl.hpp:135
T get() const
Definition click_impl.hpp:173
bool is_option() const override
Definition click_impl.hpp:279
bool accepts_missing_value() const override
Definition click_impl.hpp:273
Definition click.hpp:16
#define LOG_CRITICAL_S
CRITICAL级别流式日志的宏 / Macro for CRITICAL level stream logging.
Definition thread_logger.hpp:1333
#define LOG_WARN_S
WARN级别流式日志的宏 / Macro for WARN level stream logging.
Definition thread_logger.hpp:1331
typename optional_value_type< std::remove_cv_t< std::remove_reference_t< T > > >::type optional_value_type_t
Definition click_impl.hpp:48
constexpr bool has_istream_operator_v
Definition click_impl.hpp:69
constexpr bool is_optional_v
Definition click_impl.hpp:33
Definition click_impl.hpp:15
< 用于 std::out_of_range/For std::out_of_range
Definition click.hpp:11
bool parse_optional_value(const std::string &input, T_Optional &output)
Definition click_impl.hpp:77
Definition click_impl.hpp:26