1 /*
2
3 Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22
23 */
24
25 #ifndef CXXOPTS_HPP_INCLUDED
26 #define CXXOPTS_HPP_INCLUDED
27
28 #include <cctype>
29 #include <cstring>
30 #include <exception>
31 #include <iostream>
32 #include <limits>
33 #include <list>
34 #include <map>
35 #include <memory>
36 #include <sstream>
37 #include <string>
38 #include <unordered_map>
39 #include <unordered_set>
40 #include <utility>
41 #include <vector>
42 #include <algorithm>
43
44 #if defined(__GNUC__) && !defined(__clang__)
45 # if (__GNUC__ * 10 + __GNUC_MINOR__) < 49
46 # define CXXOPTS_NO_REGEX true
47 # endif
48 #endif
49
50 #ifndef CXXOPTS_NO_REGEX
51 # include <regex>
52 #endif // CXXOPTS_NO_REGEX
53
54 #ifdef __cpp_lib_optional
55 #include <optional>
56 #define CXXOPTS_HAS_OPTIONAL
57 #endif
58
59 #if __cplusplus >= 201603L
60 #define CXXOPTS_NODISCARD [[nodiscard]]
61 #else
62 #define CXXOPTS_NODISCARD
63 #endif
64
65 #ifndef CXXOPTS_VECTOR_DELIMITER
66 #define CXXOPTS_VECTOR_DELIMITER ','
67 #endif
68
69 #define CXXOPTS__VERSION_MAJOR 3
70 #define CXXOPTS__VERSION_MINOR 0
71 #define CXXOPTS__VERSION_PATCH 0
72
73 #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
74 #define CXXOPTS_NULL_DEREF_IGNORE
75 #endif
76
77 namespace cxxopts
78 {
79 static constexpr struct {
80 uint8_t major, minor, patch;
81 } version = {
82 CXXOPTS__VERSION_MAJOR,
83 CXXOPTS__VERSION_MINOR,
84 CXXOPTS__VERSION_PATCH
85 };
86 } // namespace cxxopts
87
88 //when we ask cxxopts to use Unicode, help strings are processed using ICU,
89 //which results in the correct lengths being computed for strings when they
90 //are formatted for the help output
91 //it is necessary to make sure that <unicode/unistr.h> can be found by the
92 //compiler, and that icu-uc is linked in to the binary.
93
94 #ifdef CXXOPTS_USE_UNICODE
95 #include <unicode/unistr.h>
96
97 namespace cxxopts
98 {
99 using String = icu::UnicodeString;
100
101 inline
102 String
toLocalString(std::string s)103 toLocalString(std::string s)
104 {
105 return icu::UnicodeString::fromUTF8(std::move(s));
106 }
107
108 #if defined(__GNUC__)
109 // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
110 // warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
111 #pragma GCC diagnostic push
112 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
113 #pragma GCC diagnostic ignored "-Weffc++"
114 // This will be ignored under other compilers like LLVM clang.
115 #endif
116 class UnicodeStringIterator : public
117 std::iterator<std::forward_iterator_tag, int32_t>
118 {
119 public:
120
UnicodeStringIterator(const icu::UnicodeString * string,int32_t pos)121 UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
122 : s(string)
123 , i(pos)
124 {
125 }
126
127 value_type
operator *() const128 operator*() const
129 {
130 return s->char32At(i);
131 }
132
133 bool
operator ==(const UnicodeStringIterator & rhs) const134 operator==(const UnicodeStringIterator& rhs) const
135 {
136 return s == rhs.s && i == rhs.i;
137 }
138
139 bool
operator !=(const UnicodeStringIterator & rhs) const140 operator!=(const UnicodeStringIterator& rhs) const
141 {
142 return !(*this == rhs);
143 }
144
145 UnicodeStringIterator&
operator ++()146 operator++()
147 {
148 ++i;
149 return *this;
150 }
151
152 UnicodeStringIterator
operator +(int32_t v)153 operator+(int32_t v)
154 {
155 return UnicodeStringIterator(s, i + v);
156 }
157
158 private:
159 const icu::UnicodeString* s;
160 int32_t i;
161 };
162 #if defined(__GNUC__)
163 #pragma GCC diagnostic pop
164 #endif
165
166 inline
167 String&
stringAppend(String & s,String a)168 stringAppend(String&s, String a)
169 {
170 return s.append(std::move(a));
171 }
172
173 inline
174 String&
stringAppend(String & s,size_t n,UChar32 c)175 stringAppend(String& s, size_t n, UChar32 c)
176 {
177 for (size_t i = 0; i != n; ++i)
178 {
179 s.append(c);
180 }
181
182 return s;
183 }
184
185 template <typename Iterator>
186 String&
stringAppend(String & s,Iterator begin,Iterator end)187 stringAppend(String& s, Iterator begin, Iterator end)
188 {
189 while (begin != end)
190 {
191 s.append(*begin);
192 ++begin;
193 }
194
195 return s;
196 }
197
198 inline
199 size_t
stringLength(const String & s)200 stringLength(const String& s)
201 {
202 return s.length();
203 }
204
205 inline
206 std::string
toUTF8String(const String & s)207 toUTF8String(const String& s)
208 {
209 std::string result;
210 s.toUTF8String(result);
211
212 return result;
213 }
214
215 inline
216 bool
empty(const String & s)217 empty(const String& s)
218 {
219 return s.isEmpty();
220 }
221 }
222
223 namespace std
224 {
225 inline
226 cxxopts::UnicodeStringIterator
begin(const icu::UnicodeString & s)227 begin(const icu::UnicodeString& s)
228 {
229 return cxxopts::UnicodeStringIterator(&s, 0);
230 }
231
232 inline
233 cxxopts::UnicodeStringIterator
end(const icu::UnicodeString & s)234 end(const icu::UnicodeString& s)
235 {
236 return cxxopts::UnicodeStringIterator(&s, s.length());
237 }
238 }
239
240 //ifdef CXXOPTS_USE_UNICODE
241 #else
242
243 namespace cxxopts
244 {
245 using String = std::string;
246
247 template <typename T>
248 T
toLocalString(T && t)249 toLocalString(T&& t)
250 {
251 return std::forward<T>(t);
252 }
253
254 inline
255 size_t
stringLength(const String & s)256 stringLength(const String& s)
257 {
258 return s.length();
259 }
260
261 inline
262 String&
stringAppend(String & s,const String & a)263 stringAppend(String&s, const String& a)
264 {
265 return s.append(a);
266 }
267
268 inline
269 String&
stringAppend(String & s,size_t n,char c)270 stringAppend(String& s, size_t n, char c)
271 {
272 return s.append(n, c);
273 }
274
275 template <typename Iterator>
276 String&
stringAppend(String & s,Iterator begin,Iterator end)277 stringAppend(String& s, Iterator begin, Iterator end)
278 {
279 return s.append(begin, end);
280 }
281
282 template <typename T>
283 std::string
toUTF8String(T && t)284 toUTF8String(T&& t)
285 {
286 return std::forward<T>(t);
287 }
288
289 inline
290 bool
empty(const std::string & s)291 empty(const std::string& s)
292 {
293 return s.empty();
294 }
295 } // namespace cxxopts
296
297 //ifdef CXXOPTS_USE_UNICODE
298 #endif
299
300 namespace cxxopts
301 {
302 namespace
303 {
304 #ifdef _WIN32
305 const std::string LQUOTE("\'");
306 const std::string RQUOTE("\'");
307 #else
308 const std::string LQUOTE("‘");
309 const std::string RQUOTE("’");
310 #endif
311 } // namespace
312
313 #if defined(__GNUC__)
314 // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
315 // warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
316 #pragma GCC diagnostic push
317 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
318 #pragma GCC diagnostic ignored "-Weffc++"
319 // This will be ignored under other compilers like LLVM clang.
320 #endif
321 class Value : public std::enable_shared_from_this<Value>
322 {
323 public:
324
325 virtual ~Value() = default;
326
327 virtual
328 std::shared_ptr<Value>
329 clone() const = 0;
330
331 virtual void
332 parse(const std::string& text) const = 0;
333
334 virtual void
335 parse() const = 0;
336
337 virtual bool
338 has_default() const = 0;
339
340 virtual bool
341 is_container() const = 0;
342
343 virtual bool
344 has_implicit() const = 0;
345
346 virtual std::string
347 get_default_value() const = 0;
348
349 virtual std::string
350 get_implicit_value() const = 0;
351
352 virtual std::shared_ptr<Value>
353 default_value(const std::string& value) = 0;
354
355 virtual std::shared_ptr<Value>
356 implicit_value(const std::string& value) = 0;
357
358 virtual std::shared_ptr<Value>
359 no_implicit_value() = 0;
360
361 virtual bool
362 is_boolean() const = 0;
363 };
364 #if defined(__GNUC__)
365 #pragma GCC diagnostic pop
366 #endif
367 class OptionException : public std::exception
368 {
369 public:
OptionException(std::string message)370 explicit OptionException(std::string message)
371 : m_message(std::move(message))
372 {
373 }
374
375 CXXOPTS_NODISCARD
376 const char*
what() const377 what() const noexcept override
378 {
379 return m_message.c_str();
380 }
381
382 private:
383 std::string m_message;
384 };
385
386 class OptionSpecException : public OptionException
387 {
388 public:
389
OptionSpecException(const std::string & message)390 explicit OptionSpecException(const std::string& message)
391 : OptionException(message)
392 {
393 }
394 };
395
396 class OptionParseException : public OptionException
397 {
398 public:
OptionParseException(const std::string & message)399 explicit OptionParseException(const std::string& message)
400 : OptionException(message)
401 {
402 }
403 };
404
405 class option_exists_error : public OptionSpecException
406 {
407 public:
option_exists_error(const std::string & option)408 explicit option_exists_error(const std::string& option)
409 : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists")
410 {
411 }
412 };
413
414 class invalid_option_format_error : public OptionSpecException
415 {
416 public:
invalid_option_format_error(const std::string & format)417 explicit invalid_option_format_error(const std::string& format)
418 : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE)
419 {
420 }
421 };
422
423 class option_syntax_exception : public OptionParseException {
424 public:
option_syntax_exception(const std::string & text)425 explicit option_syntax_exception(const std::string& text)
426 : OptionParseException("Argument " + LQUOTE + text + RQUOTE +
427 " starts with a - but has incorrect syntax")
428 {
429 }
430 };
431
432 class option_not_exists_exception : public OptionParseException
433 {
434 public:
option_not_exists_exception(const std::string & option)435 explicit option_not_exists_exception(const std::string& option)
436 : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist")
437 {
438 }
439 };
440
441 class missing_argument_exception : public OptionParseException
442 {
443 public:
missing_argument_exception(const std::string & option)444 explicit missing_argument_exception(const std::string& option)
445 : OptionParseException(
446 "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
447 )
448 {
449 }
450 };
451
452 class option_requires_argument_exception : public OptionParseException
453 {
454 public:
option_requires_argument_exception(const std::string & option)455 explicit option_requires_argument_exception(const std::string& option)
456 : OptionParseException(
457 "Option " + LQUOTE + option + RQUOTE + " requires an argument"
458 )
459 {
460 }
461 };
462
463 class option_not_has_argument_exception : public OptionParseException
464 {
465 public:
option_not_has_argument_exception(const std::string & option,const std::string & arg)466 option_not_has_argument_exception
467 (
468 const std::string& option,
469 const std::string& arg
470 )
471 : OptionParseException(
472 "Option " + LQUOTE + option + RQUOTE +
473 " does not take an argument, but argument " +
474 LQUOTE + arg + RQUOTE + " given"
475 )
476 {
477 }
478 };
479
480 class option_not_present_exception : public OptionParseException
481 {
482 public:
option_not_present_exception(const std::string & option)483 explicit option_not_present_exception(const std::string& option)
484 : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present")
485 {
486 }
487 };
488
489 class option_has_no_value_exception : public OptionException
490 {
491 public:
option_has_no_value_exception(const std::string & option)492 explicit option_has_no_value_exception(const std::string& option)
493 : OptionException(
494 !option.empty() ?
495 ("Option " + LQUOTE + option + RQUOTE + " has no value") :
496 "Option has no value")
497 {
498 }
499 };
500
501 class argument_incorrect_type : public OptionParseException
502 {
503 public:
argument_incorrect_type(const std::string & arg)504 explicit argument_incorrect_type
505 (
506 const std::string& arg
507 )
508 : OptionParseException(
509 "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
510 )
511 {
512 }
513 };
514
515 class option_required_exception : public OptionParseException
516 {
517 public:
option_required_exception(const std::string & option)518 explicit option_required_exception(const std::string& option)
519 : OptionParseException(
520 "Option " + LQUOTE + option + RQUOTE + " is required but not present"
521 )
522 {
523 }
524 };
525
526 template <typename T>
throw_or_mimic(const std::string & text)527 void throw_or_mimic(const std::string& text)
528 {
529 static_assert(std::is_base_of<std::exception, T>::value,
530 "throw_or_mimic only works on std::exception and "
531 "deriving classes");
532
533 #ifndef CXXOPTS_NO_EXCEPTIONS
534 // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
535 throw T{text};
536 #else
537 // Otherwise manually instantiate the exception, print what() to stderr,
538 // and exit
539 T exception{text};
540 std::cerr << exception.what() << std::endl;
541 std::exit(EXIT_FAILURE);
542 #endif
543 }
544
545 namespace values
546 {
547 namespace parser_tool
548 {
549 struct IntegerDesc
550 {
551 std::string negative = "";
552 std::string base = "";
553 std::string value = "";
554 };
555 struct ArguDesc {
556 std::string arg_name = "";
557 bool grouping = false;
558 bool set_value = false;
559 std::string value = "";
560 };
561 #ifdef CXXOPTS_NO_REGEX
SplitInteger(const std::string & text)562 inline IntegerDesc SplitInteger(const std::string &text)
563 {
564 if (text.empty())
565 {
566 throw_or_mimic<argument_incorrect_type>(text);
567 }
568 IntegerDesc desc;
569 const char *pdata = text.c_str();
570 if (*pdata == '-')
571 {
572 pdata += 1;
573 desc.negative = "-";
574 }
575 if (strncmp(pdata, "0x", 2) == 0)
576 {
577 pdata += 2;
578 desc.base = "0x";
579 }
580 if (*pdata != '\0')
581 {
582 desc.value = std::string(pdata);
583 }
584 else
585 {
586 throw_or_mimic<argument_incorrect_type>(text);
587 }
588 return desc;
589 }
590
IsTrueText(const std::string & text)591 inline bool IsTrueText(const std::string &text)
592 {
593 const char *pdata = text.c_str();
594 if (*pdata == 't' || *pdata == 'T')
595 {
596 pdata += 1;
597 if (strncmp(pdata, "rue\0", 4) == 0)
598 {
599 return true;
600 }
601 }
602 else if (strncmp(pdata, "1\0", 2) == 0)
603 {
604 return true;
605 }
606 return false;
607 }
608
IsFalseText(const std::string & text)609 inline bool IsFalseText(const std::string &text)
610 {
611 const char *pdata = text.c_str();
612 if (*pdata == 'f' || *pdata == 'F')
613 {
614 pdata += 1;
615 if (strncmp(pdata, "alse\0", 5) == 0)
616 {
617 return true;
618 }
619 }
620 else if (strncmp(pdata, "0\0", 2) == 0)
621 {
622 return true;
623 }
624 return false;
625 }
626
SplitSwitchDef(const std::string & text)627 inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text)
628 {
629 std::string short_sw, long_sw;
630 const char *pdata = text.c_str();
631 if (isalnum(*pdata) && *(pdata + 1) == ',') {
632 short_sw = std::string(1, *pdata);
633 pdata += 2;
634 }
635 while (*pdata == ' ') { pdata += 1; }
636 if (isalnum(*pdata)) {
637 const char *store = pdata;
638 pdata += 1;
639 while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') {
640 pdata += 1;
641 }
642 if (*pdata == '\0') {
643 long_sw = std::string(store, pdata - store);
644 } else {
645 throw_or_mimic<invalid_option_format_error>(text);
646 }
647 }
648 return std::pair<std::string, std::string>(short_sw, long_sw);
649 }
650
ParseArgument(const char * arg,bool & matched)651 inline ArguDesc ParseArgument(const char *arg, bool &matched)
652 {
653 ArguDesc argu_desc;
654 const char *pdata = arg;
655 matched = false;
656 if (strncmp(pdata, "--", 2) == 0)
657 {
658 pdata += 2;
659 if (isalnum(*pdata))
660 {
661 argu_desc.arg_name.push_back(*pdata);
662 pdata += 1;
663 while (isalnum(*pdata) || *pdata == '-' || *pdata == '_')
664 {
665 argu_desc.arg_name.push_back(*pdata);
666 pdata += 1;
667 }
668 if (argu_desc.arg_name.length() > 1)
669 {
670 if (*pdata == '=')
671 {
672 argu_desc.set_value = true;
673 pdata += 1;
674 if (*pdata != '\0')
675 {
676 argu_desc.value = std::string(pdata);
677 }
678 matched = true;
679 }
680 else if (*pdata == '\0')
681 {
682 matched = true;
683 }
684 }
685 }
686 }
687 else if (strncmp(pdata, "-", 1) == 0)
688 {
689 pdata += 1;
690 argu_desc.grouping = true;
691 while (isalnum(*pdata))
692 {
693 argu_desc.arg_name.push_back(*pdata);
694 pdata += 1;
695 }
696 matched = !argu_desc.arg_name.empty() && *pdata == '\0';
697 }
698 return argu_desc;
699 }
700
701 #else // CXXOPTS_NO_REGEX
702
703 namespace
704 {
705
706 std::basic_regex<char> integer_pattern
707 ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
708 std::basic_regex<char> truthy_pattern
709 ("(t|T)(rue)?|1");
710 std::basic_regex<char> falsy_pattern
711 ("(f|F)(alse)?|0");
712
713 std::basic_regex<char> option_matcher
714 ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
715 std::basic_regex<char> option_specifier
716 ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?");
717
718 } // namespace
719
SplitInteger(const std::string & text)720 inline IntegerDesc SplitInteger(const std::string &text)
721 {
722 std::smatch match;
723 std::regex_match(text, match, integer_pattern);
724
725 if (match.length() == 0)
726 {
727 throw_or_mimic<argument_incorrect_type>(text);
728 }
729
730 IntegerDesc desc;
731 desc.negative = match[1];
732 desc.base = match[2];
733 desc.value = match[3];
734
735 if (match.length(4) > 0)
736 {
737 desc.base = match[5];
738 desc.value = "0";
739 return desc;
740 }
741
742 return desc;
743 }
744
IsTrueText(const std::string & text)745 inline bool IsTrueText(const std::string &text)
746 {
747 std::smatch result;
748 std::regex_match(text, result, truthy_pattern);
749 return !result.empty();
750 }
751
IsFalseText(const std::string & text)752 inline bool IsFalseText(const std::string &text)
753 {
754 std::smatch result;
755 std::regex_match(text, result, falsy_pattern);
756 return !result.empty();
757 }
758
SplitSwitchDef(const std::string & text)759 inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text)
760 {
761 std::match_results<const char*> result;
762 std::regex_match(text.c_str(), result, option_specifier);
763 if (result.empty())
764 {
765 throw_or_mimic<invalid_option_format_error>(text);
766 }
767
768 const std::string& short_sw = result[2];
769 const std::string& long_sw = result[3];
770
771 return std::pair<std::string, std::string>(short_sw, long_sw);
772 }
773
ParseArgument(const char * arg,bool & matched)774 inline ArguDesc ParseArgument(const char *arg, bool &matched)
775 {
776 std::match_results<const char*> result;
777 std::regex_match(arg, result, option_matcher);
778 matched = !result.empty();
779
780 ArguDesc argu_desc;
781 if (matched) {
782 argu_desc.arg_name = result[1].str();
783 argu_desc.set_value = result[2].length() > 0;
784 argu_desc.value = result[3].str();
785 if (result[4].length() > 0)
786 {
787 argu_desc.grouping = true;
788 argu_desc.arg_name = result[4].str();
789 }
790 }
791
792 return argu_desc;
793 }
794
795 #endif // CXXOPTS_NO_REGEX
796 #undef CXXOPTS_NO_REGEX
797 }
798
799 namespace detail
800 {
801 template <typename T, bool B>
802 struct SignedCheck;
803
804 template <typename T>
805 struct SignedCheck<T, true>
806 {
807 template <typename U>
808 void
operator ()cxxopts::values::detail::SignedCheck809 operator()(bool negative, U u, const std::string& text)
810 {
811 if (negative)
812 {
813 if (u > static_cast<U>((std::numeric_limits<T>::min)()))
814 {
815 throw_or_mimic<argument_incorrect_type>(text);
816 }
817 }
818 else
819 {
820 if (u > static_cast<U>((std::numeric_limits<T>::max)()))
821 {
822 throw_or_mimic<argument_incorrect_type>(text);
823 }
824 }
825 }
826 };
827
828 template <typename T>
829 struct SignedCheck<T, false>
830 {
831 template <typename U>
832 void
operator ()cxxopts::values::detail::SignedCheck833 operator()(bool, U, const std::string&) const {}
834 };
835
836 template <typename T, typename U>
837 void
check_signed_range(bool negative,U value,const std::string & text)838 check_signed_range(bool negative, U value, const std::string& text)
839 {
840 SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
841 }
842 } // namespace detail
843
844 template <typename R, typename T>
845 void
checked_negate(R & r,T && t,const std::string &,std::true_type)846 checked_negate(R& r, T&& t, const std::string&, std::true_type)
847 {
848 // if we got to here, then `t` is a positive number that fits into
849 // `R`. So to avoid MSVC C4146, we first cast it to `R`.
850 // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
851 r = static_cast<R>(-static_cast<R>(t-1)-1);
852 }
853
854 template <typename R, typename T>
855 void
checked_negate(R &,T &&,const std::string & text,std::false_type)856 checked_negate(R&, T&&, const std::string& text, std::false_type)
857 {
858 throw_or_mimic<argument_incorrect_type>(text);
859 }
860
861 template <typename T>
862 void
integer_parser(const std::string & text,T & value)863 integer_parser(const std::string& text, T& value)
864 {
865 parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);
866
867 using US = typename std::make_unsigned<T>::type;
868 constexpr bool is_signed = std::numeric_limits<T>::is_signed;
869
870 const bool negative = int_desc.negative.length() > 0;
871 const uint8_t base = int_desc.base.length() > 0 ? 16 : 10;
872 const std::string & value_match = int_desc.value;
873
874 US result = 0;
875
876 for (char ch : value_match)
877 {
878 US digit = 0;
879
880 if (ch >= '0' && ch <= '9')
881 {
882 digit = static_cast<US>(ch - '0');
883 }
884 else if (base == 16 && ch >= 'a' && ch <= 'f')
885 {
886 digit = static_cast<US>(ch - 'a' + 10);
887 }
888 else if (base == 16 && ch >= 'A' && ch <= 'F')
889 {
890 digit = static_cast<US>(ch - 'A' + 10);
891 }
892 else
893 {
894 throw_or_mimic<argument_incorrect_type>(text);
895 }
896
897 const US next = static_cast<US>(result * base + digit);
898 if (result > next)
899 {
900 throw_or_mimic<argument_incorrect_type>(text);
901 }
902
903 result = next;
904 }
905
906 detail::check_signed_range<T>(negative, result, text);
907
908 if (negative)
909 {
910 checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
911 }
912 else
913 {
914 value = static_cast<T>(result);
915 }
916 }
917
918 template <typename T>
stringstream_parser(const std::string & text,T & value)919 void stringstream_parser(const std::string& text, T& value)
920 {
921 std::stringstream in(text);
922 in >> value;
923 if (!in) {
924 throw_or_mimic<argument_incorrect_type>(text);
925 }
926 }
927
928 template <typename T,
929 typename std::enable_if<std::is_integral<T>::value>::type* = nullptr
930 >
parse_value(const std::string & text,T & value)931 void parse_value(const std::string& text, T& value)
932 {
933 integer_parser(text, value);
934 }
935
936 inline
937 void
parse_value(const std::string & text,bool & value)938 parse_value(const std::string& text, bool& value)
939 {
940 if (parser_tool::IsTrueText(text))
941 {
942 value = true;
943 return;
944 }
945
946 if (parser_tool::IsFalseText(text))
947 {
948 value = false;
949 return;
950 }
951
952 throw_or_mimic<argument_incorrect_type>(text);
953 }
954
955 inline
956 void
parse_value(const std::string & text,std::string & value)957 parse_value(const std::string& text, std::string& value)
958 {
959 value = text;
960 }
961
962 // The fallback parser. It uses the stringstream parser to parse all types
963 // that have not been overloaded explicitly. It has to be placed in the
964 // source code before all other more specialized templates.
965 template <typename T,
966 typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr
967 >
968 void
parse_value(const std::string & text,T & value)969 parse_value(const std::string& text, T& value) {
970 stringstream_parser(text, value);
971 }
972
973 template <typename T>
974 void
parse_value(const std::string & text,std::vector<T> & value)975 parse_value(const std::string& text, std::vector<T>& value)
976 {
977 std::stringstream in(text);
978 std::string token;
979 while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
980 T v;
981 parse_value(token, v);
982 value.emplace_back(std::move(v));
983 }
984 }
985
986 #ifdef CXXOPTS_HAS_OPTIONAL
987 template <typename T>
988 void
parse_value(const std::string & text,std::optional<T> & value)989 parse_value(const std::string& text, std::optional<T>& value)
990 {
991 T result;
992 parse_value(text, result);
993 value = std::move(result);
994 }
995 #endif
996
997 inline
parse_value(const std::string & text,char & c)998 void parse_value(const std::string& text, char& c)
999 {
1000 if (text.length() != 1)
1001 {
1002 throw_or_mimic<argument_incorrect_type>(text);
1003 }
1004
1005 c = text[0];
1006 }
1007
1008 template <typename T>
1009 struct type_is_container
1010 {
1011 static constexpr bool value = false;
1012 };
1013
1014 template <typename T>
1015 struct type_is_container<std::vector<T>>
1016 {
1017 static constexpr bool value = true;
1018 };
1019
1020 template <typename T>
1021 class abstract_value : public Value
1022 {
1023 using Self = abstract_value<T>;
1024
1025 public:
abstract_value()1026 abstract_value()
1027 : m_result(std::make_shared<T>())
1028 , m_store(m_result.get())
1029 {
1030 }
1031
abstract_value(T * t)1032 explicit abstract_value(T* t)
1033 : m_store(t)
1034 {
1035 }
1036
1037 ~abstract_value() override = default;
1038
1039 abstract_value& operator=(const abstract_value&) = default;
1040
abstract_value(const abstract_value & rhs)1041 abstract_value(const abstract_value& rhs)
1042 {
1043 if (rhs.m_result)
1044 {
1045 m_result = std::make_shared<T>();
1046 m_store = m_result.get();
1047 }
1048 else
1049 {
1050 m_store = rhs.m_store;
1051 }
1052
1053 m_default = rhs.m_default;
1054 m_implicit = rhs.m_implicit;
1055 m_default_value = rhs.m_default_value;
1056 m_implicit_value = rhs.m_implicit_value;
1057 }
1058
1059 void
parse(const std::string & text) const1060 parse(const std::string& text) const override
1061 {
1062 parse_value(text, *m_store);
1063 }
1064
1065 bool
is_container() const1066 is_container() const override
1067 {
1068 return type_is_container<T>::value;
1069 }
1070
1071 void
parse() const1072 parse() const override
1073 {
1074 parse_value(m_default_value, *m_store);
1075 }
1076
1077 bool
has_default() const1078 has_default() const override
1079 {
1080 return m_default;
1081 }
1082
1083 bool
has_implicit() const1084 has_implicit() const override
1085 {
1086 return m_implicit;
1087 }
1088
1089 std::shared_ptr<Value>
default_value(const std::string & value)1090 default_value(const std::string& value) override
1091 {
1092 m_default = true;
1093 m_default_value = value;
1094 return shared_from_this();
1095 }
1096
1097 std::shared_ptr<Value>
implicit_value(const std::string & value)1098 implicit_value(const std::string& value) override
1099 {
1100 m_implicit = true;
1101 m_implicit_value = value;
1102 return shared_from_this();
1103 }
1104
1105 std::shared_ptr<Value>
no_implicit_value()1106 no_implicit_value() override
1107 {
1108 m_implicit = false;
1109 return shared_from_this();
1110 }
1111
1112 std::string
get_default_value() const1113 get_default_value() const override
1114 {
1115 return m_default_value;
1116 }
1117
1118 std::string
get_implicit_value() const1119 get_implicit_value() const override
1120 {
1121 return m_implicit_value;
1122 }
1123
1124 bool
is_boolean() const1125 is_boolean() const override
1126 {
1127 return std::is_same<T, bool>::value;
1128 }
1129
1130 const T&
get() const1131 get() const
1132 {
1133 if (m_store == nullptr)
1134 {
1135 return *m_result;
1136 }
1137 return *m_store;
1138 }
1139
1140 protected:
1141 std::shared_ptr<T> m_result{};
1142 T* m_store{};
1143
1144 bool m_default = false;
1145 bool m_implicit = false;
1146
1147 std::string m_default_value{};
1148 std::string m_implicit_value{};
1149 };
1150
1151 template <typename T>
1152 class standard_value : public abstract_value<T>
1153 {
1154 public:
1155 using abstract_value<T>::abstract_value;
1156
1157 CXXOPTS_NODISCARD
1158 std::shared_ptr<Value>
clone() const1159 clone() const override
1160 {
1161 return std::make_shared<standard_value<T>>(*this);
1162 }
1163 };
1164
1165 template <>
1166 class standard_value<bool> : public abstract_value<bool>
1167 {
1168 public:
1169 ~standard_value() override = default;
1170
standard_value()1171 standard_value()
1172 {
1173 set_default_and_implicit();
1174 }
1175
standard_value(bool * b)1176 explicit standard_value(bool* b)
1177 : abstract_value(b)
1178 {
1179 set_default_and_implicit();
1180 }
1181
1182 std::shared_ptr<Value>
clone() const1183 clone() const override
1184 {
1185 return std::make_shared<standard_value<bool>>(*this);
1186 }
1187
1188 private:
1189
1190 void
set_default_and_implicit()1191 set_default_and_implicit()
1192 {
1193 m_default = true;
1194 m_default_value = "false";
1195 m_implicit = true;
1196 m_implicit_value = "true";
1197 }
1198 };
1199 } // namespace values
1200
1201 template <typename T>
1202 std::shared_ptr<Value>
value()1203 value()
1204 {
1205 return std::make_shared<values::standard_value<T>>();
1206 }
1207
1208 template <typename T>
1209 std::shared_ptr<Value>
value(T & t)1210 value(T& t)
1211 {
1212 return std::make_shared<values::standard_value<T>>(&t);
1213 }
1214
1215 class OptionAdder;
1216
1217 class OptionDetails
1218 {
1219 public:
OptionDetails(std::string short_,std::string long_,String desc,std::shared_ptr<const Value> val)1220 OptionDetails
1221 (
1222 std::string short_,
1223 std::string long_,
1224 String desc,
1225 std::shared_ptr<const Value> val
1226 )
1227 : m_short(std::move(short_))
1228 , m_long(std::move(long_))
1229 , m_desc(std::move(desc))
1230 , m_value(std::move(val))
1231 , m_count(0)
1232 {
1233 m_hash = std::hash<std::string>{}(m_long + m_short);
1234 }
1235
OptionDetails(const OptionDetails & rhs)1236 OptionDetails(const OptionDetails& rhs)
1237 : m_desc(rhs.m_desc)
1238 , m_value(rhs.m_value->clone())
1239 , m_count(rhs.m_count)
1240 {
1241 }
1242
1243 OptionDetails(OptionDetails&& rhs) = default;
1244
1245 CXXOPTS_NODISCARD
1246 const String&
description() const1247 description() const
1248 {
1249 return m_desc;
1250 }
1251
1252 CXXOPTS_NODISCARD
1253 const Value&
value() const1254 value() const {
1255 return *m_value;
1256 }
1257
1258 CXXOPTS_NODISCARD
1259 std::shared_ptr<Value>
make_storage() const1260 make_storage() const
1261 {
1262 return m_value->clone();
1263 }
1264
1265 CXXOPTS_NODISCARD
1266 const std::string&
short_name() const1267 short_name() const
1268 {
1269 return m_short;
1270 }
1271
1272 CXXOPTS_NODISCARD
1273 const std::string&
long_name() const1274 long_name() const
1275 {
1276 return m_long;
1277 }
1278
1279 size_t
hash() const1280 hash() const
1281 {
1282 return m_hash;
1283 }
1284
1285 private:
1286 std::string m_short{};
1287 std::string m_long{};
1288 String m_desc{};
1289 std::shared_ptr<const Value> m_value{};
1290 int m_count;
1291
1292 size_t m_hash{};
1293 };
1294
1295 struct HelpOptionDetails
1296 {
1297 std::string s;
1298 std::string l;
1299 String desc;
1300 bool has_default;
1301 std::string default_value;
1302 bool has_implicit;
1303 std::string implicit_value;
1304 std::string arg_help;
1305 bool is_container;
1306 bool is_boolean;
1307 };
1308
1309 struct HelpGroupDetails
1310 {
1311 std::string name{};
1312 std::string description{};
1313 std::vector<HelpOptionDetails> options{};
1314 };
1315
1316 class OptionValue
1317 {
1318 public:
1319 void
parse(const std::shared_ptr<const OptionDetails> & details,const std::string & text)1320 parse
1321 (
1322 const std::shared_ptr<const OptionDetails>& details,
1323 const std::string& text
1324 )
1325 {
1326 ensure_value(details);
1327 ++m_count;
1328 m_value->parse(text);
1329 m_long_name = &details->long_name();
1330 }
1331
1332 void
parse_default(const std::shared_ptr<const OptionDetails> & details)1333 parse_default(const std::shared_ptr<const OptionDetails>& details)
1334 {
1335 ensure_value(details);
1336 m_default = true;
1337 m_long_name = &details->long_name();
1338 m_value->parse();
1339 }
1340
1341 void
parse_no_value(const std::shared_ptr<const OptionDetails> & details)1342 parse_no_value(const std::shared_ptr<const OptionDetails>& details)
1343 {
1344 m_long_name = &details->long_name();
1345 }
1346
1347 #if defined(CXXOPTS_NULL_DEREF_IGNORE)
1348 #pragma GCC diagnostic push
1349 #pragma GCC diagnostic ignored "-Wnull-dereference"
1350 #endif
1351
1352 CXXOPTS_NODISCARD
1353 size_t
count() const1354 count() const noexcept
1355 {
1356 return m_count;
1357 }
1358
1359 #if defined(CXXOPTS_NULL_DEREF_IGNORE)
1360 #pragma GCC diagnostic pop
1361 #endif
1362
1363 // TODO: maybe default options should count towards the number of arguments
1364 CXXOPTS_NODISCARD
1365 bool
has_default() const1366 has_default() const noexcept
1367 {
1368 return m_default;
1369 }
1370
1371 template <typename T>
1372 const T&
as() const1373 as() const
1374 {
1375 if (m_value == nullptr) {
1376 throw_or_mimic<option_has_no_value_exception>(
1377 m_long_name == nullptr ? "" : *m_long_name);
1378 }
1379
1380 #ifdef CXXOPTS_NO_RTTI
1381 return static_cast<const values::standard_value<T>&>(*m_value).get();
1382 #else
1383 return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
1384 #endif
1385 }
1386
1387 private:
1388 void
ensure_value(const std::shared_ptr<const OptionDetails> & details)1389 ensure_value(const std::shared_ptr<const OptionDetails>& details)
1390 {
1391 if (m_value == nullptr)
1392 {
1393 m_value = details->make_storage();
1394 }
1395 }
1396
1397
1398 const std::string* m_long_name = nullptr;
1399 // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
1400 // where the key has the string we point to.
1401 std::shared_ptr<Value> m_value{};
1402 size_t m_count = 0;
1403 bool m_default = false;
1404 };
1405
1406 class KeyValue
1407 {
1408 public:
KeyValue(std::string key_,std::string value_)1409 KeyValue(std::string key_, std::string value_)
1410 : m_key(std::move(key_))
1411 , m_value(std::move(value_))
1412 {
1413 }
1414
1415 CXXOPTS_NODISCARD
1416 const std::string&
key() const1417 key() const
1418 {
1419 return m_key;
1420 }
1421
1422 CXXOPTS_NODISCARD
1423 const std::string&
value() const1424 value() const
1425 {
1426 return m_value;
1427 }
1428
1429 template <typename T>
1430 T
as() const1431 as() const
1432 {
1433 T result;
1434 values::parse_value(m_value, result);
1435 return result;
1436 }
1437
1438 private:
1439 std::string m_key;
1440 std::string m_value;
1441 };
1442
1443 using ParsedHashMap = std::unordered_map<size_t, OptionValue>;
1444 using NameHashMap = std::unordered_map<std::string, size_t>;
1445
1446 class ParseResult
1447 {
1448 public:
1449
1450 ParseResult() = default;
1451 ParseResult(const ParseResult&) = default;
1452
ParseResult(NameHashMap && keys,ParsedHashMap && values,std::vector<KeyValue> sequential,std::vector<std::string> && unmatched_args)1453 ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)
1454 : m_keys(std::move(keys))
1455 , m_values(std::move(values))
1456 , m_sequential(std::move(sequential))
1457 , m_unmatched(std::move(unmatched_args))
1458 {
1459 }
1460
1461 ParseResult& operator=(ParseResult&&) = default;
1462 ParseResult& operator=(const ParseResult&) = default;
1463
1464 size_t
count(const std::string & o) const1465 count(const std::string& o) const
1466 {
1467 auto iter = m_keys.find(o);
1468 if (iter == m_keys.end())
1469 {
1470 return 0;
1471 }
1472
1473 auto viter = m_values.find(iter->second);
1474
1475 if (viter == m_values.end())
1476 {
1477 return 0;
1478 }
1479
1480 return viter->second.count();
1481 }
1482
1483 const OptionValue&
operator [](const std::string & option) const1484 operator[](const std::string& option) const
1485 {
1486 auto iter = m_keys.find(option);
1487
1488 if (iter == m_keys.end())
1489 {
1490 throw_or_mimic<option_not_present_exception>(option);
1491 }
1492
1493 auto viter = m_values.find(iter->second);
1494
1495 if (viter == m_values.end())
1496 {
1497 throw_or_mimic<option_not_present_exception>(option);
1498 }
1499
1500 return viter->second;
1501 }
1502
1503 const std::vector<KeyValue>&
arguments() const1504 arguments() const
1505 {
1506 return m_sequential;
1507 }
1508
1509 const std::vector<std::string>&
unmatched() const1510 unmatched() const
1511 {
1512 return m_unmatched;
1513 }
1514
1515 private:
1516 NameHashMap m_keys{};
1517 ParsedHashMap m_values{};
1518 std::vector<KeyValue> m_sequential{};
1519 std::vector<std::string> m_unmatched{};
1520 };
1521
1522 struct Option
1523 {
Optioncxxopts::Option1524 Option
1525 (
1526 std::string opts,
1527 std::string desc,
1528 std::shared_ptr<const Value> value = ::cxxopts::value<bool>(),
1529 std::string arg_help = ""
1530 )
1531 : opts_(std::move(opts))
1532 , desc_(std::move(desc))
1533 , value_(std::move(value))
1534 , arg_help_(std::move(arg_help))
1535 {
1536 }
1537
1538 std::string opts_;
1539 std::string desc_;
1540 std::shared_ptr<const Value> value_;
1541 std::string arg_help_;
1542 };
1543
1544 using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
1545 using PositionalList = std::vector<std::string>;
1546 using PositionalListIterator = PositionalList::const_iterator;
1547
1548 class OptionParser
1549 {
1550 public:
OptionParser(const OptionMap & options,const PositionalList & positional,bool allow_unrecognised)1551 OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
1552 : m_options(options)
1553 , m_positional(positional)
1554 , m_allow_unrecognised(allow_unrecognised)
1555 {
1556 }
1557
1558 ParseResult
1559 parse(int argc, const char* const* argv);
1560
1561 bool
1562 consume_positional(const std::string& a, PositionalListIterator& next);
1563
1564 void
1565 checked_parse_arg
1566 (
1567 int argc,
1568 const char* const* argv,
1569 int& current,
1570 const std::shared_ptr<OptionDetails>& value,
1571 const std::string& name
1572 );
1573
1574 void
1575 add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
1576
1577 void
1578 parse_option
1579 (
1580 const std::shared_ptr<OptionDetails>& value,
1581 const std::string& name,
1582 const std::string& arg = ""
1583 );
1584
1585 void
1586 parse_default(const std::shared_ptr<OptionDetails>& details);
1587
1588 void
1589 parse_no_value(const std::shared_ptr<OptionDetails>& details);
1590
1591 private:
1592
1593 void finalise_aliases();
1594
1595 const OptionMap& m_options;
1596 const PositionalList& m_positional;
1597
1598 std::vector<KeyValue> m_sequential{};
1599 bool m_allow_unrecognised;
1600
1601 ParsedHashMap m_parsed{};
1602 NameHashMap m_keys{};
1603 };
1604
1605 class Options
1606 {
1607 public:
1608
Options(std::string program,std::string help_string="")1609 explicit Options(std::string program, std::string help_string = "")
1610 : m_program(std::move(program))
1611 , m_help_string(toLocalString(std::move(help_string)))
1612 , m_custom_help("[OPTION...]")
1613 , m_positional_help("positional parameters")
1614 , m_show_positional(false)
1615 , m_allow_unrecognised(false)
1616 , m_width(76)
1617 , m_tab_expansion(false)
1618 , m_options(std::make_shared<OptionMap>())
1619 {
1620 }
1621
1622 Options&
positional_help(std::string help_text)1623 positional_help(std::string help_text)
1624 {
1625 m_positional_help = std::move(help_text);
1626 return *this;
1627 }
1628
1629 Options&
custom_help(std::string help_text)1630 custom_help(std::string help_text)
1631 {
1632 m_custom_help = std::move(help_text);
1633 return *this;
1634 }
1635
1636 Options&
show_positional_help()1637 show_positional_help()
1638 {
1639 m_show_positional = true;
1640 return *this;
1641 }
1642
1643 Options&
allow_unrecognised_options()1644 allow_unrecognised_options()
1645 {
1646 m_allow_unrecognised = true;
1647 return *this;
1648 }
1649
1650 Options&
set_width(size_t width)1651 set_width(size_t width)
1652 {
1653 m_width = width;
1654 return *this;
1655 }
1656
1657 Options&
set_tab_expansion(bool expansion=true)1658 set_tab_expansion(bool expansion=true)
1659 {
1660 m_tab_expansion = expansion;
1661 return *this;
1662 }
1663
1664 ParseResult
1665 parse(int argc, const char* const* argv);
1666
1667 OptionAdder
1668 add_options(std::string group = "");
1669
1670 void
1671 add_options
1672 (
1673 const std::string& group,
1674 std::initializer_list<Option> options
1675 );
1676
1677 void
1678 add_option
1679 (
1680 const std::string& group,
1681 const Option& option
1682 );
1683
1684 void
1685 add_option
1686 (
1687 const std::string& group,
1688 const std::string& s,
1689 const std::string& l,
1690 std::string desc,
1691 const std::shared_ptr<const Value>& value,
1692 std::string arg_help
1693 );
1694
1695 //parse positional arguments into the given option
1696 void
1697 parse_positional(std::string option);
1698
1699 void
1700 parse_positional(std::vector<std::string> options);
1701
1702 void
1703 parse_positional(std::initializer_list<std::string> options);
1704
1705 template <typename Iterator>
1706 void
parse_positional(Iterator begin,Iterator end)1707 parse_positional(Iterator begin, Iterator end) {
1708 parse_positional(std::vector<std::string>{begin, end});
1709 }
1710
1711 std::string
1712 help(const std::vector<std::string>& groups = {}) const;
1713
1714 std::vector<std::string>
1715 groups() const;
1716
1717 const HelpGroupDetails&
1718 group_help(const std::string& group) const;
1719
1720 private:
1721
1722 void
1723 add_one_option
1724 (
1725 const std::string& option,
1726 const std::shared_ptr<OptionDetails>& details
1727 );
1728
1729 String
1730 help_one_group(const std::string& group) const;
1731
1732 void
1733 generate_group_help
1734 (
1735 String& result,
1736 const std::vector<std::string>& groups
1737 ) const;
1738
1739 void
1740 generate_all_groups_help(String& result) const;
1741
1742 std::string m_program{};
1743 String m_help_string{};
1744 std::string m_custom_help{};
1745 std::string m_positional_help{};
1746 bool m_show_positional;
1747 bool m_allow_unrecognised;
1748 size_t m_width;
1749 bool m_tab_expansion;
1750
1751 std::shared_ptr<OptionMap> m_options;
1752 std::vector<std::string> m_positional{};
1753 std::unordered_set<std::string> m_positional_set{};
1754
1755 //mapping from groups to help options
1756 std::map<std::string, HelpGroupDetails> m_help{};
1757
1758 std::list<OptionDetails> m_option_list{};
1759 std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{};
1760 };
1761
1762 class OptionAdder
1763 {
1764 public:
1765
OptionAdder(Options & options,std::string group)1766 OptionAdder(Options& options, std::string group)
1767 : m_options(options), m_group(std::move(group))
1768 {
1769 }
1770
1771 OptionAdder&
1772 operator()
1773 (
1774 const std::string& opts,
1775 const std::string& desc,
1776 const std::shared_ptr<const Value>& value
1777 = ::cxxopts::value<bool>(),
1778 std::string arg_help = ""
1779 );
1780
1781 private:
1782 Options& m_options;
1783 std::string m_group;
1784 };
1785
1786 namespace
1787 {
1788 constexpr size_t OPTION_LONGEST = 30;
1789 constexpr size_t OPTION_DESC_GAP = 2;
1790
1791 String
format_option(const HelpOptionDetails & o)1792 format_option
1793 (
1794 const HelpOptionDetails& o
1795 )
1796 {
1797 const auto& s = o.s;
1798 const auto& l = o.l;
1799
1800 String result = " ";
1801
1802 if (!s.empty())
1803 {
1804 result += "-" + toLocalString(s);
1805 if (!l.empty())
1806 {
1807 result += ",";
1808 }
1809 }
1810 else
1811 {
1812 result += " ";
1813 }
1814
1815 if (!l.empty())
1816 {
1817 result += " --" + toLocalString(l);
1818 }
1819
1820 auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
1821
1822 if (!o.is_boolean)
1823 {
1824 if (o.has_implicit)
1825 {
1826 result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
1827 }
1828 else
1829 {
1830 result += " " + arg;
1831 }
1832 }
1833
1834 return result;
1835 }
1836
1837 String
format_description(const HelpOptionDetails & o,size_t start,size_t allowed,bool tab_expansion)1838 format_description
1839 (
1840 const HelpOptionDetails& o,
1841 size_t start,
1842 size_t allowed,
1843 bool tab_expansion
1844 )
1845 {
1846 auto desc = o.desc;
1847
1848 if (o.has_default && (!o.is_boolean || o.default_value != "false"))
1849 {
1850 if(!o.default_value.empty())
1851 {
1852 desc += toLocalString(" (default: " + o.default_value + ")");
1853 }
1854 else
1855 {
1856 desc += toLocalString(" (default: \"\")");
1857 }
1858 }
1859
1860 String result;
1861
1862 if (tab_expansion)
1863 {
1864 String desc2;
1865 auto size = size_t{ 0 };
1866 for (auto c = std::begin(desc); c != std::end(desc); ++c)
1867 {
1868 if (*c == '\n')
1869 {
1870 desc2 += *c;
1871 size = 0;
1872 }
1873 else if (*c == '\t')
1874 {
1875 auto skip = 8 - size % 8;
1876 stringAppend(desc2, skip, ' ');
1877 size += skip;
1878 }
1879 else
1880 {
1881 desc2 += *c;
1882 ++size;
1883 }
1884 }
1885 desc = desc2;
1886 }
1887
1888 desc += " ";
1889
1890 auto current = std::begin(desc);
1891 auto previous = current;
1892 auto startLine = current;
1893 auto lastSpace = current;
1894
1895 auto size = size_t{};
1896
1897 bool appendNewLine;
1898 bool onlyWhiteSpace = true;
1899
1900 while (current != std::end(desc))
1901 {
1902 appendNewLine = false;
1903
1904 if (std::isblank(*previous))
1905 {
1906 lastSpace = current;
1907 }
1908
1909 if (!std::isblank(*current))
1910 {
1911 onlyWhiteSpace = false;
1912 }
1913
1914 while (*current == '\n')
1915 {
1916 previous = current;
1917 ++current;
1918 appendNewLine = true;
1919 }
1920
1921 if (!appendNewLine && size >= allowed)
1922 {
1923 if (lastSpace != startLine)
1924 {
1925 current = lastSpace;
1926 previous = current;
1927 }
1928 appendNewLine = true;
1929 }
1930
1931 if (appendNewLine)
1932 {
1933 stringAppend(result, startLine, current);
1934 startLine = current;
1935 lastSpace = current;
1936
1937 if (*previous != '\n')
1938 {
1939 stringAppend(result, "\n");
1940 }
1941
1942 stringAppend(result, start, ' ');
1943
1944 if (*previous != '\n')
1945 {
1946 stringAppend(result, lastSpace, current);
1947 }
1948
1949 onlyWhiteSpace = true;
1950 size = 0;
1951 }
1952
1953 previous = current;
1954 ++current;
1955 ++size;
1956 }
1957
1958 //append whatever is left but ignore whitespace
1959 if (!onlyWhiteSpace)
1960 {
1961 stringAppend(result, startLine, previous);
1962 }
1963
1964 return result;
1965 }
1966 } // namespace
1967
1968 inline
1969 void
add_options(const std::string & group,std::initializer_list<Option> options)1970 Options::add_options
1971 (
1972 const std::string &group,
1973 std::initializer_list<Option> options
1974 )
1975 {
1976 OptionAdder option_adder(*this, group);
1977 for (const auto &option: options)
1978 {
1979 option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
1980 }
1981 }
1982
1983 inline
1984 OptionAdder
add_options(std::string group)1985 Options::add_options(std::string group)
1986 {
1987 return OptionAdder(*this, std::move(group));
1988 }
1989
1990 inline
1991 OptionAdder&
operator ()(const std::string & opts,const std::string & desc,const std::shared_ptr<const Value> & value,std::string arg_help)1992 OptionAdder::operator()
1993 (
1994 const std::string& opts,
1995 const std::string& desc,
1996 const std::shared_ptr<const Value>& value,
1997 std::string arg_help
1998 )
1999 {
2000 std::string short_sw, long_sw;
2001 std::tie(short_sw, long_sw) = values::parser_tool::SplitSwitchDef(opts);
2002
2003 if (!short_sw.length() && !long_sw.length())
2004 {
2005 throw_or_mimic<invalid_option_format_error>(opts);
2006 }
2007 else if (long_sw.length() == 1 && short_sw.length())
2008 {
2009 throw_or_mimic<invalid_option_format_error>(opts);
2010 }
2011
2012 auto option_names = []
2013 (
2014 const std::string &short_,
2015 const std::string &long_
2016 )
2017 {
2018 if (long_.length() == 1)
2019 {
2020 return std::make_tuple(long_, short_);
2021 }
2022 return std::make_tuple(short_, long_);
2023 }(short_sw, long_sw);
2024
2025 m_options.add_option
2026 (
2027 m_group,
2028 std::get<0>(option_names),
2029 std::get<1>(option_names),
2030 desc,
2031 value,
2032 std::move(arg_help)
2033 );
2034
2035 return *this;
2036 }
2037
2038 inline
2039 void
parse_default(const std::shared_ptr<OptionDetails> & details)2040 OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)
2041 {
2042 // TODO: remove the duplicate code here
2043 auto& store = m_parsed[details->hash()];
2044 store.parse_default(details);
2045 }
2046
2047 inline
2048 void
parse_no_value(const std::shared_ptr<OptionDetails> & details)2049 OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details)
2050 {
2051 auto& store = m_parsed[details->hash()];
2052 store.parse_no_value(details);
2053 }
2054
2055 inline
2056 void
parse_option(const std::shared_ptr<OptionDetails> & value,const std::string &,const std::string & arg)2057 OptionParser::parse_option
2058 (
2059 const std::shared_ptr<OptionDetails>& value,
2060 const std::string& /*name*/,
2061 const std::string& arg
2062 )
2063 {
2064 auto hash = value->hash();
2065 auto& result = m_parsed[hash];
2066 result.parse(value, arg);
2067
2068 m_sequential.emplace_back(value->long_name(), arg);
2069 }
2070
2071 inline
2072 void
checked_parse_arg(int argc,const char * const * argv,int & current,const std::shared_ptr<OptionDetails> & value,const std::string & name)2073 OptionParser::checked_parse_arg
2074 (
2075 int argc,
2076 const char* const* argv,
2077 int& current,
2078 const std::shared_ptr<OptionDetails>& value,
2079 const std::string& name
2080 )
2081 {
2082 if (current + 1 >= argc)
2083 {
2084 if (value->value().has_implicit())
2085 {
2086 parse_option(value, name, value->value().get_implicit_value());
2087 }
2088 else
2089 {
2090 throw_or_mimic<missing_argument_exception>(name);
2091 }
2092 }
2093 else
2094 {
2095 if (value->value().has_implicit())
2096 {
2097 parse_option(value, name, value->value().get_implicit_value());
2098 }
2099 else
2100 {
2101 parse_option(value, name, argv[current + 1]);
2102 ++current;
2103 }
2104 }
2105 }
2106
2107 inline
2108 void
add_to_option(OptionMap::const_iterator iter,const std::string & option,const std::string & arg)2109 OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
2110 {
2111 parse_option(iter->second, option, arg);
2112 }
2113
2114 inline
2115 bool
consume_positional(const std::string & a,PositionalListIterator & next)2116 OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
2117 {
2118 while (next != m_positional.end())
2119 {
2120 auto iter = m_options.find(*next);
2121 if (iter != m_options.end())
2122 {
2123 if (!iter->second->value().is_container())
2124 {
2125 auto& result = m_parsed[iter->second->hash()];
2126 if (result.count() == 0)
2127 {
2128 add_to_option(iter, *next, a);
2129 ++next;
2130 return true;
2131 }
2132 ++next;
2133 continue;
2134 }
2135 add_to_option(iter, *next, a);
2136 return true;
2137 }
2138 throw_or_mimic<option_not_exists_exception>(*next);
2139 }
2140
2141 return false;
2142 }
2143
2144 inline
2145 void
parse_positional(std::string option)2146 Options::parse_positional(std::string option)
2147 {
2148 parse_positional(std::vector<std::string>{std::move(option)});
2149 }
2150
2151 inline
2152 void
parse_positional(std::vector<std::string> options)2153 Options::parse_positional(std::vector<std::string> options)
2154 {
2155 m_positional = std::move(options);
2156
2157 m_positional_set.insert(m_positional.begin(), m_positional.end());
2158 }
2159
2160 inline
2161 void
parse_positional(std::initializer_list<std::string> options)2162 Options::parse_positional(std::initializer_list<std::string> options)
2163 {
2164 parse_positional(std::vector<std::string>(options));
2165 }
2166
2167 inline
2168 ParseResult
parse(int argc,const char * const * argv)2169 Options::parse(int argc, const char* const* argv)
2170 {
2171 OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
2172
2173 return parser.parse(argc, argv);
2174 }
2175
2176 inline ParseResult
parse(int argc,const char * const * argv)2177 OptionParser::parse(int argc, const char* const* argv)
2178 {
2179 int current = 1;
2180 bool consume_remaining = false;
2181 auto next_positional = m_positional.begin();
2182
2183 std::vector<std::string> unmatched;
2184
2185 while (current != argc)
2186 {
2187 if (strcmp(argv[current], "--") == 0)
2188 {
2189 consume_remaining = true;
2190 ++current;
2191 break;
2192 }
2193 bool matched = false;
2194 values::parser_tool::ArguDesc argu_desc =
2195 values::parser_tool::ParseArgument(argv[current], matched);
2196
2197 if (!matched)
2198 {
2199 //not a flag
2200
2201 // but if it starts with a `-`, then it's an error
2202 if (argv[current][0] == '-' && argv[current][1] != '\0') {
2203 if (!m_allow_unrecognised) {
2204 throw_or_mimic<option_syntax_exception>(argv[current]);
2205 }
2206 }
2207
2208 //if true is returned here then it was consumed, otherwise it is
2209 //ignored
2210 if (consume_positional(argv[current], next_positional))
2211 {
2212 }
2213 else
2214 {
2215 unmatched.emplace_back(argv[current]);
2216 }
2217 //if we return from here then it was parsed successfully, so continue
2218 }
2219 else
2220 {
2221 //short or long option?
2222 if (argu_desc.grouping)
2223 {
2224 const std::string& s = argu_desc.arg_name;
2225
2226 for (std::size_t i = 0; i != s.size(); ++i)
2227 {
2228 std::string name(1, s[i]);
2229 auto iter = m_options.find(name);
2230
2231 if (iter == m_options.end())
2232 {
2233 if (m_allow_unrecognised)
2234 {
2235 continue;
2236 }
2237 //error
2238 throw_or_mimic<option_not_exists_exception>(name);
2239 }
2240
2241 auto value = iter->second;
2242
2243 if (i + 1 == s.size())
2244 {
2245 //it must be the last argument
2246 checked_parse_arg(argc, argv, current, value, name);
2247 }
2248 else if (value->value().has_implicit())
2249 {
2250 parse_option(value, name, value->value().get_implicit_value());
2251 }
2252 else if (i + 1 < s.size())
2253 {
2254 std::string arg_value = s.substr(i + 1);
2255 parse_option(value, name, arg_value);
2256 break;
2257 }
2258 else
2259 {
2260 //error
2261 throw_or_mimic<option_requires_argument_exception>(name);
2262 }
2263 }
2264 }
2265 else if (argu_desc.arg_name.length() != 0)
2266 {
2267 const std::string& name = argu_desc.arg_name;
2268
2269 auto iter = m_options.find(name);
2270
2271 if (iter == m_options.end())
2272 {
2273 if (m_allow_unrecognised)
2274 {
2275 // keep unrecognised options in argument list, skip to next argument
2276 unmatched.emplace_back(argv[current]);
2277 ++current;
2278 continue;
2279 }
2280 //error
2281 throw_or_mimic<option_not_exists_exception>(name);
2282 }
2283
2284 auto opt = iter->second;
2285
2286 //equals provided for long option?
2287 if (argu_desc.set_value)
2288 {
2289 //parse the option given
2290
2291 parse_option(opt, name, argu_desc.value);
2292 }
2293 else
2294 {
2295 //parse the next argument
2296 checked_parse_arg(argc, argv, current, opt, name);
2297 }
2298 }
2299
2300 }
2301
2302 ++current;
2303 }
2304
2305 for (auto& opt : m_options)
2306 {
2307 auto& detail = opt.second;
2308 const auto& value = detail->value();
2309
2310 auto& store = m_parsed[detail->hash()];
2311
2312 if (value.has_default()) {
2313 if (!store.count() && !store.has_default()) {
2314 parse_default(detail);
2315 }
2316 }
2317 else {
2318 parse_no_value(detail);
2319 }
2320 }
2321
2322 if (consume_remaining)
2323 {
2324 while (current < argc)
2325 {
2326 if (!consume_positional(argv[current], next_positional)) {
2327 break;
2328 }
2329 ++current;
2330 }
2331
2332 //adjust argv for any that couldn't be swallowed
2333 while (current != argc) {
2334 unmatched.emplace_back(argv[current]);
2335 ++current;
2336 }
2337 }
2338
2339 finalise_aliases();
2340
2341 ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched));
2342 return parsed;
2343 }
2344
2345 inline
2346 void
finalise_aliases()2347 OptionParser::finalise_aliases()
2348 {
2349 for (auto& option: m_options)
2350 {
2351 auto& detail = *option.second;
2352 auto hash = detail.hash();
2353 m_keys[detail.short_name()] = hash;
2354 m_keys[detail.long_name()] = hash;
2355
2356 m_parsed.emplace(hash, OptionValue());
2357 }
2358 }
2359
2360 inline
2361 void
add_option(const std::string & group,const Option & option)2362 Options::add_option
2363 (
2364 const std::string& group,
2365 const Option& option
2366 )
2367 {
2368 add_options(group, {option});
2369 }
2370
2371 inline
2372 void
add_option(const std::string & group,const std::string & s,const std::string & l,std::string desc,const std::shared_ptr<const Value> & value,std::string arg_help)2373 Options::add_option
2374 (
2375 const std::string& group,
2376 const std::string& s,
2377 const std::string& l,
2378 std::string desc,
2379 const std::shared_ptr<const Value>& value,
2380 std::string arg_help
2381 )
2382 {
2383 auto stringDesc = toLocalString(std::move(desc));
2384 auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
2385
2386 if (!s.empty())
2387 {
2388 add_one_option(s, option);
2389 }
2390
2391 if (!l.empty())
2392 {
2393 add_one_option(l, option);
2394 }
2395
2396 m_option_list.push_front(*option.get());
2397 auto iter = m_option_list.begin();
2398 m_option_map[s] = iter;
2399 m_option_map[l] = iter;
2400
2401 //add the help details
2402 auto& options = m_help[group];
2403
2404 options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
2405 value->has_default(), value->get_default_value(),
2406 value->has_implicit(), value->get_implicit_value(),
2407 std::move(arg_help),
2408 value->is_container(),
2409 value->is_boolean()});
2410 }
2411
2412 inline
2413 void
add_one_option(const std::string & option,const std::shared_ptr<OptionDetails> & details)2414 Options::add_one_option
2415 (
2416 const std::string& option,
2417 const std::shared_ptr<OptionDetails>& details
2418 )
2419 {
2420 auto in = m_options->emplace(option, details);
2421
2422 if (!in.second)
2423 {
2424 throw_or_mimic<option_exists_error>(option);
2425 }
2426 }
2427
2428 inline
2429 String
help_one_group(const std::string & g) const2430 Options::help_one_group(const std::string& g) const
2431 {
2432 using OptionHelp = std::vector<std::pair<String, String>>;
2433
2434 auto group = m_help.find(g);
2435 if (group == m_help.end())
2436 {
2437 return "";
2438 }
2439
2440 OptionHelp format;
2441
2442 size_t longest = 0;
2443
2444 String result;
2445
2446 if (!g.empty())
2447 {
2448 result += toLocalString(" " + g + " options:\n");
2449 }
2450
2451 for (const auto& o : group->second.options)
2452 {
2453 if (m_positional_set.find(o.l) != m_positional_set.end() &&
2454 !m_show_positional)
2455 {
2456 continue;
2457 }
2458
2459 auto s = format_option(o);
2460 longest = (std::max)(longest, stringLength(s));
2461 format.push_back(std::make_pair(s, String()));
2462 }
2463 longest = (std::min)(longest, OPTION_LONGEST);
2464
2465 //widest allowed description -- min 10 chars for helptext/line
2466 size_t allowed = 10;
2467 if (m_width > allowed + longest + OPTION_DESC_GAP)
2468 {
2469 allowed = m_width - longest - OPTION_DESC_GAP;
2470 }
2471
2472 auto fiter = format.begin();
2473 for (const auto& o : group->second.options)
2474 {
2475 if (m_positional_set.find(o.l) != m_positional_set.end() &&
2476 !m_show_positional)
2477 {
2478 continue;
2479 }
2480
2481 auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
2482
2483 result += fiter->first;
2484 if (stringLength(fiter->first) > longest)
2485 {
2486 result += '\n';
2487 result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
2488 }
2489 else
2490 {
2491 result += toLocalString(std::string(longest + OPTION_DESC_GAP -
2492 stringLength(fiter->first),
2493 ' '));
2494 }
2495 result += d;
2496 result += '\n';
2497
2498 ++fiter;
2499 }
2500
2501 return result;
2502 }
2503
2504 inline
2505 void
generate_group_help(String & result,const std::vector<std::string> & print_groups) const2506 Options::generate_group_help
2507 (
2508 String& result,
2509 const std::vector<std::string>& print_groups
2510 ) const
2511 {
2512 for (size_t i = 0; i != print_groups.size(); ++i)
2513 {
2514 const String& group_help_text = help_one_group(print_groups[i]);
2515 if (empty(group_help_text))
2516 {
2517 continue;
2518 }
2519 result += group_help_text;
2520 if (i < print_groups.size() - 1)
2521 {
2522 result += '\n';
2523 }
2524 }
2525 }
2526
2527 inline
2528 void
generate_all_groups_help(String & result) const2529 Options::generate_all_groups_help(String& result) const
2530 {
2531 std::vector<std::string> all_groups;
2532
2533 std::transform(
2534 m_help.begin(),
2535 m_help.end(),
2536 std::back_inserter(all_groups),
2537 [] (const std::map<std::string, HelpGroupDetails>::value_type& group)
2538 {
2539 return group.first;
2540 }
2541 );
2542
2543 generate_group_help(result, all_groups);
2544 }
2545
2546 inline
2547 std::string
help(const std::vector<std::string> & help_groups) const2548 Options::help(const std::vector<std::string>& help_groups) const
2549 {
2550 String result = m_help_string + "\nUsage:\n " +
2551 toLocalString(m_program) + " " + toLocalString(m_custom_help);
2552
2553 if (!m_positional.empty() && !m_positional_help.empty()) {
2554 result += " " + toLocalString(m_positional_help);
2555 }
2556
2557 result += "\n\n";
2558
2559 if (help_groups.empty())
2560 {
2561 generate_all_groups_help(result);
2562 }
2563 else
2564 {
2565 generate_group_help(result, help_groups);
2566 }
2567
2568 return toUTF8String(result);
2569 }
2570
2571 inline
2572 std::vector<std::string>
groups() const2573 Options::groups() const
2574 {
2575 std::vector<std::string> g;
2576
2577 std::transform(
2578 m_help.begin(),
2579 m_help.end(),
2580 std::back_inserter(g),
2581 [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
2582 {
2583 return pair.first;
2584 }
2585 );
2586
2587 return g;
2588 }
2589
2590 inline
2591 const HelpGroupDetails&
group_help(const std::string & group) const2592 Options::group_help(const std::string& group) const
2593 {
2594 return m_help.at(group);
2595 }
2596
2597 } // namespace cxxopts
2598
2599 #endif //CXXOPTS_HPP_INCLUDED
2600