blob: 5cebc4bd173664acca0555278163f2e583a65145 [file] [log] [blame]
Jan Eilers7e989832020-06-19 11:47:21 +01001/*
2
3Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
4
Jim Flynn6217c3d2022-06-14 10:58:23 +01005SPDX-License-Identifier: MIT
6
Jan Eilers7e989832020-06-19 11:47:21 +01007Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in
15all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23THE SOFTWARE.
24
25*/
26
27#ifndef CXXOPTS_HPP_INCLUDED
28#define CXXOPTS_HPP_INCLUDED
29
30#include <cctype>
31#include <cstring>
32#include <exception>
33#include <iostream>
34#include <limits>
Matthew Sloyan84dc8432020-10-06 16:06:07 +010035#include <list>
Jan Eilers7e989832020-06-19 11:47:21 +010036#include <map>
37#include <memory>
38#include <regex>
39#include <sstream>
40#include <string>
41#include <unordered_map>
42#include <unordered_set>
43#include <utility>
44#include <vector>
45
46#ifdef __cpp_lib_optional
47#include <optional>
48#define CXXOPTS_HAS_OPTIONAL
49#endif
50
Matthew Sloyan84dc8432020-10-06 16:06:07 +010051#if __cplusplus >= 201603L
52#define CXXOPTS_NODISCARD [[nodiscard]]
53#else
54#define CXXOPTS_NODISCARD
55#endif
56
Jan Eilers7e989832020-06-19 11:47:21 +010057#ifndef CXXOPTS_VECTOR_DELIMITER
58#define CXXOPTS_VECTOR_DELIMITER ','
59#endif
60
Matthew Sloyan84dc8432020-10-06 16:06:07 +010061#define CXXOPTS__VERSION_MAJOR 3
62#define CXXOPTS__VERSION_MINOR 0
Jan Eilers7e989832020-06-19 11:47:21 +010063#define CXXOPTS__VERSION_PATCH 0
64
65namespace cxxopts
66{
67 static constexpr struct {
68 uint8_t major, minor, patch;
69 } version = {
70 CXXOPTS__VERSION_MAJOR,
71 CXXOPTS__VERSION_MINOR,
72 CXXOPTS__VERSION_PATCH
73 };
74} // namespace cxxopts
75
76//when we ask cxxopts to use Unicode, help strings are processed using ICU,
77//which results in the correct lengths being computed for strings when they
78//are formatted for the help output
79//it is necessary to make sure that <unicode/unistr.h> can be found by the
80//compiler, and that icu-uc is linked in to the binary.
81
82#ifdef CXXOPTS_USE_UNICODE
83#include <unicode/unistr.h>
84
85namespace cxxopts
86{
87 typedef icu::UnicodeString String;
88
89 inline
90 String
91 toLocalString(std::string s)
92 {
93 return icu::UnicodeString::fromUTF8(std::move(s));
94 }
95
96 class UnicodeStringIterator : public
97 std::iterator<std::forward_iterator_tag, int32_t>
98 {
99 public:
100
101 UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
102 : s(string)
103 , i(pos)
104 {
105 }
106
107 value_type
108 operator*() const
109 {
110 return s->char32At(i);
111 }
112
113 bool
114 operator==(const UnicodeStringIterator& rhs) const
115 {
116 return s == rhs.s && i == rhs.i;
117 }
118
119 bool
120 operator!=(const UnicodeStringIterator& rhs) const
121 {
122 return !(*this == rhs);
123 }
124
125 UnicodeStringIterator&
126 operator++()
127 {
128 ++i;
129 return *this;
130 }
131
132 UnicodeStringIterator
133 operator+(int32_t v)
134 {
135 return UnicodeStringIterator(s, i + v);
136 }
137
138 private:
139 const icu::UnicodeString* s;
140 int32_t i;
141 };
142
143 inline
144 String&
145 stringAppend(String&s, String a)
146 {
147 return s.append(std::move(a));
148 }
149
150 inline
151 String&
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100152 stringAppend(String& s, size_t n, UChar32 c)
Jan Eilers7e989832020-06-19 11:47:21 +0100153 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100154 for (size_t i = 0; i != n; ++i)
Jan Eilers7e989832020-06-19 11:47:21 +0100155 {
156 s.append(c);
157 }
158
159 return s;
160 }
161
162 template <typename Iterator>
163 String&
164 stringAppend(String& s, Iterator begin, Iterator end)
165 {
166 while (begin != end)
167 {
168 s.append(*begin);
169 ++begin;
170 }
171
172 return s;
173 }
174
175 inline
176 size_t
177 stringLength(const String& s)
178 {
179 return s.length();
180 }
181
182 inline
183 std::string
184 toUTF8String(const String& s)
185 {
186 std::string result;
187 s.toUTF8String(result);
188
189 return result;
190 }
191
192 inline
193 bool
194 empty(const String& s)
195 {
196 return s.isEmpty();
197 }
198}
199
200namespace std
201{
202 inline
203 cxxopts::UnicodeStringIterator
204 begin(const icu::UnicodeString& s)
205 {
206 return cxxopts::UnicodeStringIterator(&s, 0);
207 }
208
209 inline
210 cxxopts::UnicodeStringIterator
211 end(const icu::UnicodeString& s)
212 {
213 return cxxopts::UnicodeStringIterator(&s, s.length());
214 }
215}
216
217//ifdef CXXOPTS_USE_UNICODE
218#else
219
220namespace cxxopts
221{
222 typedef std::string String;
223
224 template <typename T>
225 T
226 toLocalString(T&& t)
227 {
228 return std::forward<T>(t);
229 }
230
231 inline
232 size_t
233 stringLength(const String& s)
234 {
235 return s.length();
236 }
237
238 inline
239 String&
240 stringAppend(String&s, const String& a)
241 {
242 return s.append(a);
243 }
244
245 inline
246 String&
247 stringAppend(String& s, size_t n, char c)
248 {
249 return s.append(n, c);
250 }
251
252 template <typename Iterator>
253 String&
254 stringAppend(String& s, Iterator begin, Iterator end)
255 {
256 return s.append(begin, end);
257 }
258
259 template <typename T>
260 std::string
261 toUTF8String(T&& t)
262 {
263 return std::forward<T>(t);
264 }
265
266 inline
267 bool
268 empty(const std::string& s)
269 {
270 return s.empty();
271 }
272} // namespace cxxopts
273
274//ifdef CXXOPTS_USE_UNICODE
275#endif
276
277namespace cxxopts
278{
279 namespace
280 {
281#ifdef _WIN32
282 const std::string LQUOTE("\'");
283 const std::string RQUOTE("\'");
284#else
285 const std::string LQUOTE("‘");
286 const std::string RQUOTE("’");
287#endif
288 } // namespace
289
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100290#if defined(__GNUC__)
291// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
292// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
293#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
294#pragma GCC diagnostic push
295// This will be ignored under other compilers like LLVM clang.
296#endif
Jan Eilers7e989832020-06-19 11:47:21 +0100297 class Value : public std::enable_shared_from_this<Value>
298 {
299 public:
300
301 virtual ~Value() = default;
302
303 virtual
304 std::shared_ptr<Value>
305 clone() const = 0;
306
307 virtual void
308 parse(const std::string& text) const = 0;
309
310 virtual void
311 parse() const = 0;
312
313 virtual bool
314 has_default() const = 0;
315
316 virtual bool
317 is_container() const = 0;
318
319 virtual bool
320 has_implicit() const = 0;
321
322 virtual std::string
323 get_default_value() const = 0;
324
325 virtual std::string
326 get_implicit_value() const = 0;
327
328 virtual std::shared_ptr<Value>
329 default_value(const std::string& value) = 0;
330
331 virtual std::shared_ptr<Value>
332 implicit_value(const std::string& value) = 0;
333
334 virtual std::shared_ptr<Value>
335 no_implicit_value() = 0;
336
337 virtual bool
338 is_boolean() const = 0;
339 };
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100340#if defined(__GNUC__)
341#pragma GCC diagnostic pop
342#endif
Jan Eilers7e989832020-06-19 11:47:21 +0100343 class OptionException : public std::exception
344 {
345 public:
346 explicit OptionException(std::string message)
347 : m_message(std::move(message))
348 {
349 }
350
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100351 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +0100352 const char*
353 what() const noexcept override
354 {
355 return m_message.c_str();
356 }
357
358 private:
359 std::string m_message;
360 };
361
362 class OptionSpecException : public OptionException
363 {
364 public:
365
366 explicit OptionSpecException(const std::string& message)
367 : OptionException(message)
368 {
369 }
370 };
371
372 class OptionParseException : public OptionException
373 {
374 public:
375 explicit OptionParseException(const std::string& message)
376 : OptionException(message)
377 {
378 }
379 };
380
381 class option_exists_error : public OptionSpecException
382 {
383 public:
384 explicit option_exists_error(const std::string& option)
385 : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists")
386 {
387 }
388 };
389
390 class invalid_option_format_error : public OptionSpecException
391 {
392 public:
393 explicit invalid_option_format_error(const std::string& format)
394 : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE)
395 {
396 }
397 };
398
399 class option_syntax_exception : public OptionParseException {
400 public:
401 explicit option_syntax_exception(const std::string& text)
402 : OptionParseException("Argument " + LQUOTE + text + RQUOTE +
403 " starts with a - but has incorrect syntax")
404 {
405 }
406 };
407
408 class option_not_exists_exception : public OptionParseException
409 {
410 public:
411 explicit option_not_exists_exception(const std::string& option)
412 : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist")
413 {
414 }
415 };
416
417 class missing_argument_exception : public OptionParseException
418 {
419 public:
420 explicit missing_argument_exception(const std::string& option)
421 : OptionParseException(
422 "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
423 )
424 {
425 }
426 };
427
428 class option_requires_argument_exception : public OptionParseException
429 {
430 public:
431 explicit option_requires_argument_exception(const std::string& option)
432 : OptionParseException(
433 "Option " + LQUOTE + option + RQUOTE + " requires an argument"
434 )
435 {
436 }
437 };
438
439 class option_not_has_argument_exception : public OptionParseException
440 {
441 public:
442 option_not_has_argument_exception
443 (
444 const std::string& option,
445 const std::string& arg
446 )
447 : OptionParseException(
448 "Option " + LQUOTE + option + RQUOTE +
449 " does not take an argument, but argument " +
450 LQUOTE + arg + RQUOTE + " given"
451 )
452 {
453 }
454 };
455
456 class option_not_present_exception : public OptionParseException
457 {
458 public:
459 explicit option_not_present_exception(const std::string& option)
460 : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present")
461 {
462 }
463 };
464
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100465 class option_has_no_value_exception : public OptionException
466 {
467 public:
468 explicit option_has_no_value_exception(const std::string& option)
469 : OptionException(
470 option.empty() ?
471 ("Option " + LQUOTE + option + RQUOTE + " has no value") :
472 "Option has no value")
473 {
474 }
475 };
476
Jan Eilers7e989832020-06-19 11:47:21 +0100477 class argument_incorrect_type : public OptionParseException
478 {
479 public:
480 explicit argument_incorrect_type
481 (
482 const std::string& arg
483 )
484 : OptionParseException(
485 "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
486 )
487 {
488 }
489 };
490
491 class option_required_exception : public OptionParseException
492 {
493 public:
494 explicit option_required_exception(const std::string& option)
495 : OptionParseException(
496 "Option " + LQUOTE + option + RQUOTE + " is required but not present"
497 )
498 {
499 }
500 };
501
502 template <typename T>
503 void throw_or_mimic(const std::string& text)
504 {
505 static_assert(std::is_base_of<std::exception, T>::value,
506 "throw_or_mimic only works on std::exception and "
507 "deriving classes");
508
509#ifndef CXXOPTS_NO_EXCEPTIONS
510 // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
511 throw T{text};
512#else
513 // Otherwise manually instantiate the exception, print what() to stderr,
514 // and exit
515 T exception{text};
516 std::cerr << exception.what() << std::endl;
517 std::exit(EXIT_FAILURE);
518#endif
519 }
520
521 namespace values
522 {
523 namespace
524 {
525 std::basic_regex<char> integer_pattern
526 ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
527 std::basic_regex<char> truthy_pattern
528 ("(t|T)(rue)?|1");
529 std::basic_regex<char> falsy_pattern
530 ("(f|F)(alse)?|0");
531 } // namespace
532
533 namespace detail
534 {
535 template <typename T, bool B>
536 struct SignedCheck;
537
538 template <typename T>
539 struct SignedCheck<T, true>
540 {
541 template <typename U>
542 void
543 operator()(bool negative, U u, const std::string& text)
544 {
545 if (negative)
546 {
547 if (u > static_cast<U>((std::numeric_limits<T>::min)()))
548 {
549 throw_or_mimic<argument_incorrect_type>(text);
550 }
551 }
552 else
553 {
554 if (u > static_cast<U>((std::numeric_limits<T>::max)()))
555 {
556 throw_or_mimic<argument_incorrect_type>(text);
557 }
558 }
559 }
560 };
561
562 template <typename T>
563 struct SignedCheck<T, false>
564 {
565 template <typename U>
566 void
567 operator()(bool, U, const std::string&) {}
568 };
569
570 template <typename T, typename U>
571 void
572 check_signed_range(bool negative, U value, const std::string& text)
573 {
574 SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
575 }
576 } // namespace detail
577
578 template <typename R, typename T>
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100579 void
580 checked_negate(R& r, T&& t, const std::string&, std::true_type)
Jan Eilers7e989832020-06-19 11:47:21 +0100581 {
582 // if we got to here, then `t` is a positive number that fits into
583 // `R`. So to avoid MSVC C4146, we first cast it to `R`.
584 // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100585 r = static_cast<R>(-static_cast<R>(t-1)-1);
Jan Eilers7e989832020-06-19 11:47:21 +0100586 }
587
588 template <typename R, typename T>
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100589 void
590 checked_negate(R&, T&&, const std::string& text, std::false_type)
Jan Eilers7e989832020-06-19 11:47:21 +0100591 {
592 throw_or_mimic<argument_incorrect_type>(text);
Jan Eilers7e989832020-06-19 11:47:21 +0100593 }
594
595 template <typename T>
596 void
597 integer_parser(const std::string& text, T& value)
598 {
599 std::smatch match;
600 std::regex_match(text, match, integer_pattern);
601
602 if (match.length() == 0)
603 {
604 throw_or_mimic<argument_incorrect_type>(text);
605 }
606
607 if (match.length(4) > 0)
608 {
609 value = 0;
610 return;
611 }
612
613 using US = typename std::make_unsigned<T>::type;
614
615 constexpr bool is_signed = std::numeric_limits<T>::is_signed;
616 const bool negative = match.length(1) > 0;
617 const uint8_t base = match.length(2) > 0 ? 16 : 10;
618
619 auto value_match = match[3];
620
621 US result = 0;
622
623 for (auto iter = value_match.first; iter != value_match.second; ++iter)
624 {
625 US digit = 0;
626
627 if (*iter >= '0' && *iter <= '9')
628 {
629 digit = static_cast<US>(*iter - '0');
630 }
631 else if (base == 16 && *iter >= 'a' && *iter <= 'f')
632 {
633 digit = static_cast<US>(*iter - 'a' + 10);
634 }
635 else if (base == 16 && *iter >= 'A' && *iter <= 'F')
636 {
637 digit = static_cast<US>(*iter - 'A' + 10);
638 }
639 else
640 {
641 throw_or_mimic<argument_incorrect_type>(text);
642 }
643
644 const US next = static_cast<US>(result * base + digit);
645 if (result > next)
646 {
647 throw_or_mimic<argument_incorrect_type>(text);
648 }
649
650 result = next;
651 }
652
653 detail::check_signed_range<T>(negative, result, text);
654
655 if (negative)
656 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100657 checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
Jan Eilers7e989832020-06-19 11:47:21 +0100658 }
659 else
660 {
661 value = static_cast<T>(result);
662 }
663 }
664
665 template <typename T>
666 void stringstream_parser(const std::string& text, T& value)
667 {
668 std::stringstream in(text);
669 in >> value;
670 if (!in) {
671 throw_or_mimic<argument_incorrect_type>(text);
672 }
673 }
674
675 inline
676 void
677 parse_value(const std::string& text, uint8_t& value)
678 {
679 integer_parser(text, value);
680 }
681
682 inline
683 void
684 parse_value(const std::string& text, int8_t& value)
685 {
686 integer_parser(text, value);
687 }
688
689 inline
690 void
691 parse_value(const std::string& text, uint16_t& value)
692 {
693 integer_parser(text, value);
694 }
695
696 inline
697 void
698 parse_value(const std::string& text, int16_t& value)
699 {
700 integer_parser(text, value);
701 }
702
703 inline
704 void
705 parse_value(const std::string& text, uint32_t& value)
706 {
707 integer_parser(text, value);
708 }
709
710 inline
711 void
712 parse_value(const std::string& text, int32_t& value)
713 {
714 integer_parser(text, value);
715 }
716
717 inline
718 void
719 parse_value(const std::string& text, uint64_t& value)
720 {
721 integer_parser(text, value);
722 }
723
724 inline
725 void
726 parse_value(const std::string& text, int64_t& value)
727 {
728 integer_parser(text, value);
729 }
730
731 inline
732 void
733 parse_value(const std::string& text, bool& value)
734 {
735 std::smatch result;
736 std::regex_match(text, result, truthy_pattern);
737
738 if (!result.empty())
739 {
740 value = true;
741 return;
742 }
743
744 std::regex_match(text, result, falsy_pattern);
745 if (!result.empty())
746 {
747 value = false;
748 return;
749 }
750
751 throw_or_mimic<argument_incorrect_type>(text);
752 }
753
754 inline
755 void
756 parse_value(const std::string& text, std::string& value)
757 {
758 value = text;
759 }
760
761 // The fallback parser. It uses the stringstream parser to parse all types
762 // that have not been overloaded explicitly. It has to be placed in the
763 // source code before all other more specialized templates.
764 template <typename T>
765 void
766 parse_value(const std::string& text, T& value) {
767 stringstream_parser(text, value);
768 }
769
770 template <typename T>
771 void
772 parse_value(const std::string& text, std::vector<T>& value)
773 {
774 std::stringstream in(text);
775 std::string token;
776 while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
777 T v;
778 parse_value(token, v);
779 value.emplace_back(std::move(v));
780 }
781 }
782
783#ifdef CXXOPTS_HAS_OPTIONAL
784 template <typename T>
785 void
786 parse_value(const std::string& text, std::optional<T>& value)
787 {
788 T result;
789 parse_value(text, result);
790 value = std::move(result);
791 }
792#endif
793
794 inline
795 void parse_value(const std::string& text, char& c)
796 {
797 if (text.length() != 1)
798 {
799 throw_or_mimic<argument_incorrect_type>(text);
800 }
801
802 c = text[0];
803 }
804
805 template <typename T>
806 struct type_is_container
807 {
808 static constexpr bool value = false;
809 };
810
811 template <typename T>
812 struct type_is_container<std::vector<T>>
813 {
814 static constexpr bool value = true;
815 };
816
817 template <typename T>
818 class abstract_value : public Value
819 {
820 using Self = abstract_value<T>;
821
822 public:
823 abstract_value()
824 : m_result(std::make_shared<T>())
825 , m_store(m_result.get())
826 {
827 }
828
829 explicit abstract_value(T* t)
830 : m_store(t)
831 {
832 }
833
834 ~abstract_value() override = default;
835
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100836 abstract_value& operator=(const abstract_value&) = default;
837
Jan Eilers7e989832020-06-19 11:47:21 +0100838 abstract_value(const abstract_value& rhs)
839 {
840 if (rhs.m_result)
841 {
842 m_result = std::make_shared<T>();
843 m_store = m_result.get();
844 }
845 else
846 {
847 m_store = rhs.m_store;
848 }
849
850 m_default = rhs.m_default;
851 m_implicit = rhs.m_implicit;
852 m_default_value = rhs.m_default_value;
853 m_implicit_value = rhs.m_implicit_value;
854 }
855
856 void
857 parse(const std::string& text) const override
858 {
859 parse_value(text, *m_store);
860 }
861
862 bool
863 is_container() const override
864 {
865 return type_is_container<T>::value;
866 }
867
868 void
869 parse() const override
870 {
871 parse_value(m_default_value, *m_store);
872 }
873
874 bool
875 has_default() const override
876 {
877 return m_default;
878 }
879
880 bool
881 has_implicit() const override
882 {
883 return m_implicit;
884 }
885
886 std::shared_ptr<Value>
887 default_value(const std::string& value) override
888 {
889 m_default = true;
890 m_default_value = value;
891 return shared_from_this();
892 }
893
894 std::shared_ptr<Value>
895 implicit_value(const std::string& value) override
896 {
897 m_implicit = true;
898 m_implicit_value = value;
899 return shared_from_this();
900 }
901
902 std::shared_ptr<Value>
903 no_implicit_value() override
904 {
905 m_implicit = false;
906 return shared_from_this();
907 }
908
909 std::string
910 get_default_value() const override
911 {
912 return m_default_value;
913 }
914
915 std::string
916 get_implicit_value() const override
917 {
918 return m_implicit_value;
919 }
920
921 bool
922 is_boolean() const override
923 {
924 return std::is_same<T, bool>::value;
925 }
926
927 const T&
928 get() const
929 {
930 if (m_store == nullptr)
931 {
932 return *m_result;
933 }
934 return *m_store;
935 }
936
937 protected:
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100938 std::shared_ptr<T> m_result{};
939 T* m_store{};
Jan Eilers7e989832020-06-19 11:47:21 +0100940
941 bool m_default = false;
942 bool m_implicit = false;
943
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100944 std::string m_default_value{};
945 std::string m_implicit_value{};
Jan Eilers7e989832020-06-19 11:47:21 +0100946 };
947
948 template <typename T>
949 class standard_value : public abstract_value<T>
950 {
951 public:
952 using abstract_value<T>::abstract_value;
953
Matthew Sloyan84dc8432020-10-06 16:06:07 +0100954 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +0100955 std::shared_ptr<Value>
956 clone() const
957 {
958 return std::make_shared<standard_value<T>>(*this);
959 }
960 };
961
962 template <>
963 class standard_value<bool> : public abstract_value<bool>
964 {
965 public:
966 ~standard_value() override = default;
967
968 standard_value()
969 {
970 set_default_and_implicit();
971 }
972
973 explicit standard_value(bool* b)
974 : abstract_value(b)
975 {
976 set_default_and_implicit();
977 }
978
979 std::shared_ptr<Value>
980 clone() const override
981 {
982 return std::make_shared<standard_value<bool>>(*this);
983 }
984
985 private:
986
987 void
988 set_default_and_implicit()
989 {
990 m_default = true;
991 m_default_value = "false";
992 m_implicit = true;
993 m_implicit_value = "true";
994 }
995 };
996 } // namespace values
997
998 template <typename T>
999 std::shared_ptr<Value>
1000 value()
1001 {
1002 return std::make_shared<values::standard_value<T>>();
1003 }
1004
1005 template <typename T>
1006 std::shared_ptr<Value>
1007 value(T& t)
1008 {
1009 return std::make_shared<values::standard_value<T>>(&t);
1010 }
1011
1012 class OptionAdder;
1013
1014 class OptionDetails
1015 {
1016 public:
1017 OptionDetails
1018 (
1019 std::string short_,
1020 std::string long_,
1021 String desc,
1022 std::shared_ptr<const Value> val
1023 )
1024 : m_short(std::move(short_))
1025 , m_long(std::move(long_))
1026 , m_desc(std::move(desc))
1027 , m_value(std::move(val))
1028 , m_count(0)
1029 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001030 m_hash = std::hash<std::string>{}(m_long + m_short);
Jan Eilers7e989832020-06-19 11:47:21 +01001031 }
1032
1033 OptionDetails(const OptionDetails& rhs)
1034 : m_desc(rhs.m_desc)
1035 , m_count(rhs.m_count)
1036 {
1037 m_value = rhs.m_value->clone();
1038 }
1039
1040 OptionDetails(OptionDetails&& rhs) = default;
1041
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001042 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +01001043 const String&
1044 description() const
1045 {
1046 return m_desc;
1047 }
1048
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001049 CXXOPTS_NODISCARD
1050 const Value&
1051 value() const {
Jan Eilers7e989832020-06-19 11:47:21 +01001052 return *m_value;
1053 }
1054
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001055 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +01001056 std::shared_ptr<Value>
1057 make_storage() const
1058 {
1059 return m_value->clone();
1060 }
1061
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001062 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +01001063 const std::string&
1064 short_name() const
1065 {
1066 return m_short;
1067 }
1068
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001069 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +01001070 const std::string&
1071 long_name() const
1072 {
1073 return m_long;
1074 }
1075
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001076 size_t
1077 hash() const
1078 {
1079 return m_hash;
1080 }
1081
Jan Eilers7e989832020-06-19 11:47:21 +01001082 private:
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001083 std::string m_short{};
1084 std::string m_long{};
1085 String m_desc{};
1086 std::shared_ptr<const Value> m_value{};
Jan Eilers7e989832020-06-19 11:47:21 +01001087 int m_count;
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001088
1089 size_t m_hash{};
Jan Eilers7e989832020-06-19 11:47:21 +01001090 };
1091
1092 struct HelpOptionDetails
1093 {
1094 std::string s;
1095 std::string l;
1096 String desc;
1097 bool has_default;
1098 std::string default_value;
1099 bool has_implicit;
1100 std::string implicit_value;
1101 std::string arg_help;
1102 bool is_container;
1103 bool is_boolean;
1104 };
1105
1106 struct HelpGroupDetails
1107 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001108 std::string name{};
1109 std::string description{};
1110 std::vector<HelpOptionDetails> options{};
Jan Eilers7e989832020-06-19 11:47:21 +01001111 };
1112
1113 class OptionValue
1114 {
1115 public:
1116 void
1117 parse
1118 (
1119 const std::shared_ptr<const OptionDetails>& details,
1120 const std::string& text
1121 )
1122 {
1123 ensure_value(details);
1124 ++m_count;
1125 m_value->parse(text);
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001126 m_long_name = &details->long_name();
Jan Eilers7e989832020-06-19 11:47:21 +01001127 }
1128
1129 void
1130 parse_default(const std::shared_ptr<const OptionDetails>& details)
1131 {
1132 ensure_value(details);
1133 m_default = true;
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001134 m_long_name = &details->long_name();
Jan Eilers7e989832020-06-19 11:47:21 +01001135 m_value->parse();
1136 }
1137
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001138 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +01001139 size_t
1140 count() const noexcept
1141 {
1142 return m_count;
1143 }
1144
1145 // TODO: maybe default options should count towards the number of arguments
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001146 CXXOPTS_NODISCARD
Jan Eilers7e989832020-06-19 11:47:21 +01001147 bool
1148 has_default() const noexcept
1149 {
1150 return m_default;
1151 }
1152
1153 template <typename T>
1154 const T&
1155 as() const
1156 {
1157 if (m_value == nullptr) {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001158 throw_or_mimic<option_has_no_value_exception>(
1159 m_long_name == nullptr ? "" : *m_long_name);
Jan Eilers7e989832020-06-19 11:47:21 +01001160 }
1161
1162#ifdef CXXOPTS_NO_RTTI
1163 return static_cast<const values::standard_value<T>&>(*m_value).get();
1164#else
1165 return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
1166#endif
1167 }
1168
1169 private:
1170 void
1171 ensure_value(const std::shared_ptr<const OptionDetails>& details)
1172 {
1173 if (m_value == nullptr)
1174 {
1175 m_value = details->make_storage();
1176 }
1177 }
1178
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001179
1180 const std::string* m_long_name = nullptr;
1181 // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
1182 // where the key has the string we point to.
1183 std::shared_ptr<Value> m_value{};
Jan Eilers7e989832020-06-19 11:47:21 +01001184 size_t m_count = 0;
1185 bool m_default = false;
1186 };
1187
1188 class KeyValue
1189 {
1190 public:
1191 KeyValue(std::string key_, std::string value_)
1192 : m_key(std::move(key_))
1193 , m_value(std::move(value_))
1194 {
1195 }
1196
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001197 CXXOPTS_NODISCARD
1198 const std::string&
Jan Eilers7e989832020-06-19 11:47:21 +01001199 key() const
1200 {
1201 return m_key;
1202 }
1203
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001204 CXXOPTS_NODISCARD
1205 const std::string&
Jan Eilers7e989832020-06-19 11:47:21 +01001206 value() const
1207 {
1208 return m_value;
1209 }
1210
1211 template <typename T>
1212 T
1213 as() const
1214 {
1215 T result;
1216 values::parse_value(m_value, result);
1217 return result;
1218 }
1219
1220 private:
1221 std::string m_key;
1222 std::string m_value;
1223 };
1224
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001225 using ParsedHashMap = std::unordered_map<size_t, OptionValue>;
1226 using NameHashMap = std::unordered_map<std::string, size_t>;
1227
Jan Eilers7e989832020-06-19 11:47:21 +01001228 class ParseResult
1229 {
1230 public:
1231
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001232 ParseResult() {}
Jim Flynn6217c3d2022-06-14 10:58:23 +01001233
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001234 ParseResult(const ParseResult&) = default;
1235
1236 ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)
1237 : m_keys(std::move(keys))
1238 , m_values(std::move(values))
1239 , m_sequential(std::move(sequential))
1240 , m_unmatched(std::move(unmatched_args))
1241 {
1242 }
1243
1244 ParseResult& operator=(ParseResult&&) = default;
1245 ParseResult& operator=(const ParseResult&) = default;
Jan Eilers7e989832020-06-19 11:47:21 +01001246
1247 size_t
1248 count(const std::string& o) const
1249 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001250 auto iter = m_keys.find(o);
1251 if (iter == m_keys.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001252 {
1253 return 0;
1254 }
1255
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001256 auto viter = m_values.find(iter->second);
Jan Eilers7e989832020-06-19 11:47:21 +01001257
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001258 if (viter == m_values.end())
1259 {
1260 return 0;
1261 }
1262
1263 return viter->second.count();
Jan Eilers7e989832020-06-19 11:47:21 +01001264 }
1265
1266 const OptionValue&
1267 operator[](const std::string& option) const
1268 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001269 auto iter = m_keys.find(option);
Jan Eilers7e989832020-06-19 11:47:21 +01001270
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001271 if (iter == m_keys.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001272 {
1273 throw_or_mimic<option_not_present_exception>(option);
1274 }
1275
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001276 auto viter = m_values.find(iter->second);
Jan Eilers7e989832020-06-19 11:47:21 +01001277
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001278 if (viter == m_values.end())
1279 {
1280 throw_or_mimic<option_not_present_exception>(option);
1281 }
1282
1283 return viter->second;
Jan Eilers7e989832020-06-19 11:47:21 +01001284 }
1285
1286 const std::vector<KeyValue>&
1287 arguments() const
1288 {
1289 return m_sequential;
1290 }
1291
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001292 const std::vector<std::string>&
1293 unmatched() const
1294 {
1295 return m_unmatched;
1296 }
1297
Jan Eilers7e989832020-06-19 11:47:21 +01001298 private:
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001299 NameHashMap m_keys{};
1300 ParsedHashMap m_values{};
1301 std::vector<KeyValue> m_sequential{};
1302 std::vector<std::string> m_unmatched{};
Jan Eilers7e989832020-06-19 11:47:21 +01001303 };
1304
1305 struct Option
1306 {
1307 Option
1308 (
1309 std::string opts,
1310 std::string desc,
1311 std::shared_ptr<const Value> value = ::cxxopts::value<bool>(),
1312 std::string arg_help = ""
1313 )
1314 : opts_(std::move(opts))
1315 , desc_(std::move(desc))
1316 , value_(std::move(value))
1317 , arg_help_(std::move(arg_help))
1318 {
1319 }
1320
1321 std::string opts_;
1322 std::string desc_;
1323 std::shared_ptr<const Value> value_;
1324 std::string arg_help_;
1325 };
1326
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001327 using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
1328 using PositionalList = std::vector<std::string>;
1329 using PositionalListIterator = PositionalList::const_iterator;
1330
1331 class OptionParser
1332 {
1333 public:
1334 OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
1335 : m_options(options)
1336 , m_positional(positional)
1337 , m_allow_unrecognised(allow_unrecognised)
1338 {
1339 }
1340
1341 ParseResult
1342 parse(int argc, const char* const* argv);
1343
1344 bool
1345 consume_positional(const std::string& a, PositionalListIterator& next);
1346
1347 void
1348 checked_parse_arg
1349 (
1350 int argc,
1351 const char* const* argv,
1352 int& current,
1353 const std::shared_ptr<OptionDetails>& value,
1354 const std::string& name
1355 );
1356
1357 void
1358 add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
1359
1360 void
1361 parse_option
1362 (
1363 const std::shared_ptr<OptionDetails>& value,
1364 const std::string& name,
1365 const std::string& arg = ""
1366 );
1367
1368 void
1369 parse_default(const std::shared_ptr<OptionDetails>& details);
1370
1371 private:
1372
1373 void finalise_aliases();
1374
1375 const OptionMap& m_options;
1376 const PositionalList& m_positional;
1377
1378 std::vector<KeyValue> m_sequential{};
1379 bool m_allow_unrecognised;
1380
1381 ParsedHashMap m_parsed{};
1382 NameHashMap m_keys{};
1383 };
1384
Jan Eilers7e989832020-06-19 11:47:21 +01001385 class Options
1386 {
Jan Eilers7e989832020-06-19 11:47:21 +01001387 public:
1388
1389 explicit Options(std::string program, std::string help_string = "")
1390 : m_program(std::move(program))
1391 , m_help_string(toLocalString(std::move(help_string)))
1392 , m_custom_help("[OPTION...]")
1393 , m_positional_help("positional parameters")
1394 , m_show_positional(false)
1395 , m_allow_unrecognised(false)
1396 , m_options(std::make_shared<OptionMap>())
Jan Eilers7e989832020-06-19 11:47:21 +01001397 {
1398 }
1399
1400 Options&
1401 positional_help(std::string help_text)
1402 {
1403 m_positional_help = std::move(help_text);
1404 return *this;
1405 }
1406
1407 Options&
1408 custom_help(std::string help_text)
1409 {
1410 m_custom_help = std::move(help_text);
1411 return *this;
1412 }
1413
1414 Options&
1415 show_positional_help()
1416 {
1417 m_show_positional = true;
1418 return *this;
1419 }
1420
1421 Options&
1422 allow_unrecognised_options()
1423 {
1424 m_allow_unrecognised = true;
1425 return *this;
1426 }
1427
1428 ParseResult
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001429 parse(int argc, const char* const* argv);
Jan Eilers7e989832020-06-19 11:47:21 +01001430
1431 OptionAdder
1432 add_options(std::string group = "");
1433
1434 void
1435 add_options
1436 (
1437 const std::string& group,
1438 std::initializer_list<Option> options
1439 );
1440
1441 void
1442 add_option
1443 (
1444 const std::string& group,
1445 const Option& option
1446 );
1447
1448 void
1449 add_option
1450 (
1451 const std::string& group,
1452 const std::string& s,
1453 const std::string& l,
1454 std::string desc,
1455 const std::shared_ptr<const Value>& value,
1456 std::string arg_help
1457 );
1458
1459 //parse positional arguments into the given option
1460 void
1461 parse_positional(std::string option);
1462
1463 void
1464 parse_positional(std::vector<std::string> options);
1465
1466 void
1467 parse_positional(std::initializer_list<std::string> options);
1468
1469 template <typename Iterator>
1470 void
1471 parse_positional(Iterator begin, Iterator end) {
1472 parse_positional(std::vector<std::string>{begin, end});
1473 }
1474
1475 std::string
1476 help(const std::vector<std::string>& groups = {}) const;
1477
1478 std::vector<std::string>
1479 groups() const;
1480
1481 const HelpGroupDetails&
1482 group_help(const std::string& group) const;
1483
1484 private:
1485
1486 void
1487 add_one_option
1488 (
1489 const std::string& option,
1490 const std::shared_ptr<OptionDetails>& details
1491 );
1492
1493 String
1494 help_one_group(const std::string& group) const;
1495
1496 void
1497 generate_group_help
1498 (
1499 String& result,
1500 const std::vector<std::string>& groups
1501 ) const;
1502
1503 void
1504 generate_all_groups_help(String& result) const;
1505
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001506 std::string m_program{};
1507 String m_help_string{};
1508 std::string m_custom_help{};
1509 std::string m_positional_help{};
Jan Eilers7e989832020-06-19 11:47:21 +01001510 bool m_show_positional;
1511 bool m_allow_unrecognised;
1512
1513 std::shared_ptr<OptionMap> m_options;
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001514 std::vector<std::string> m_positional{};
1515 std::unordered_set<std::string> m_positional_set{};
Jan Eilers7e989832020-06-19 11:47:21 +01001516
1517 //mapping from groups to help options
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001518 std::map<std::string, HelpGroupDetails> m_help{};
1519
1520 std::list<OptionDetails> m_option_list{};
1521 std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{};
Jan Eilers7e989832020-06-19 11:47:21 +01001522 };
1523
1524 class OptionAdder
1525 {
1526 public:
1527
1528 OptionAdder(Options& options, std::string group)
1529 : m_options(options), m_group(std::move(group))
1530 {
1531 }
1532
1533 OptionAdder&
1534 operator()
1535 (
1536 const std::string& opts,
1537 const std::string& desc,
1538 const std::shared_ptr<const Value>& value
1539 = ::cxxopts::value<bool>(),
1540 std::string arg_help = ""
1541 );
1542
1543 private:
1544 Options& m_options;
1545 std::string m_group;
1546 };
1547
1548 namespace
1549 {
1550 constexpr int OPTION_LONGEST = 30;
1551 constexpr int OPTION_DESC_GAP = 2;
1552
1553 std::basic_regex<char> option_matcher
1554 ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
1555
1556 std::basic_regex<char> option_specifier
1557 ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?");
1558
1559 String
1560 format_option
1561 (
1562 const HelpOptionDetails& o
1563 )
1564 {
1565 const auto& s = o.s;
1566 const auto& l = o.l;
1567
1568 String result = " ";
1569
1570 if (!s.empty())
1571 {
1572 result += "-" + toLocalString(s);
1573 if (!l.empty())
1574 {
1575 result += ",";
1576 }
1577 }
1578 else
1579 {
1580 result += " ";
1581 }
1582
1583 if (!l.empty())
1584 {
1585 result += " --" + toLocalString(l);
1586 }
1587
1588 auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
1589
1590 if (!o.is_boolean)
1591 {
1592 if (o.has_implicit)
1593 {
1594 result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
1595 }
1596 else
1597 {
1598 result += " " + arg;
1599 }
1600 }
1601
1602 return result;
1603 }
1604
1605 String
1606 format_description
1607 (
1608 const HelpOptionDetails& o,
1609 size_t start,
1610 size_t width
1611 )
1612 {
1613 auto desc = o.desc;
1614
1615 if (o.has_default && (!o.is_boolean || o.default_value != "false"))
1616 {
1617 if(!o.default_value.empty())
1618 {
1619 desc += toLocalString(" (default: " + o.default_value + ")");
1620 }
1621 else
1622 {
1623 desc += toLocalString(" (default: \"\")");
1624 }
1625 }
1626
1627 String result;
1628
1629 auto current = std::begin(desc);
1630 auto startLine = current;
1631 auto lastSpace = current;
1632
1633 auto size = size_t{};
1634
1635 while (current != std::end(desc))
1636 {
1637 if (*current == ' ')
1638 {
1639 lastSpace = current;
1640 }
1641
1642 if (*current == '\n')
1643 {
1644 startLine = current + 1;
1645 lastSpace = startLine;
1646 }
1647 else if (size > width)
1648 {
1649 if (lastSpace == startLine)
1650 {
1651 stringAppend(result, startLine, current + 1);
1652 stringAppend(result, "\n");
1653 stringAppend(result, start, ' ');
1654 startLine = current + 1;
1655 lastSpace = startLine;
1656 }
1657 else
1658 {
1659 stringAppend(result, startLine, lastSpace);
1660 stringAppend(result, "\n");
1661 stringAppend(result, start, ' ');
1662 startLine = lastSpace + 1;
1663 lastSpace = startLine;
1664 }
1665 size = 0;
1666 }
1667 else
1668 {
1669 ++size;
1670 }
1671
1672 ++current;
1673 }
1674
1675 //append whatever is left
1676 stringAppend(result, startLine, current);
1677
1678 return result;
1679 }
1680 } // namespace
1681
1682inline
Jan Eilers7e989832020-06-19 11:47:21 +01001683void
1684Options::add_options
1685(
1686 const std::string &group,
1687 std::initializer_list<Option> options
1688)
1689{
1690 OptionAdder option_adder(*this, group);
1691 for (const auto &option: options)
1692 {
1693 option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
1694 }
1695}
1696
1697inline
1698OptionAdder
1699Options::add_options(std::string group)
1700{
1701 return OptionAdder(*this, std::move(group));
1702}
1703
1704inline
1705OptionAdder&
1706OptionAdder::operator()
1707(
1708 const std::string& opts,
1709 const std::string& desc,
1710 const std::shared_ptr<const Value>& value,
1711 std::string arg_help
1712)
1713{
1714 std::match_results<const char*> result;
1715 std::regex_match(opts.c_str(), result, option_specifier);
1716
1717 if (result.empty())
1718 {
1719 throw_or_mimic<invalid_option_format_error>(opts);
1720 }
1721
1722 const auto& short_match = result[2];
1723 const auto& long_match = result[3];
1724
1725 if (!short_match.length() && !long_match.length())
1726 {
1727 throw_or_mimic<invalid_option_format_error>(opts);
1728 } else if (long_match.length() == 1 && short_match.length())
1729 {
1730 throw_or_mimic<invalid_option_format_error>(opts);
1731 }
1732
1733 auto option_names = []
1734 (
1735 const std::sub_match<const char*>& short_,
1736 const std::sub_match<const char*>& long_
1737 )
1738 {
1739 if (long_.length() == 1)
1740 {
1741 return std::make_tuple(long_.str(), short_.str());
1742 }
1743 return std::make_tuple(short_.str(), long_.str());
1744 }(short_match, long_match);
1745
1746 m_options.add_option
1747 (
1748 m_group,
1749 std::get<0>(option_names),
1750 std::get<1>(option_names),
1751 desc,
1752 value,
1753 std::move(arg_help)
1754 );
1755
1756 return *this;
1757}
1758
1759inline
1760void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001761OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)
Jan Eilers7e989832020-06-19 11:47:21 +01001762{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001763 // TODO: remove the duplicate code here
1764 auto& store = m_parsed[details->hash()];
1765 store.parse_default(details);
Jan Eilers7e989832020-06-19 11:47:21 +01001766}
1767
1768inline
1769void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001770OptionParser::parse_option
Jan Eilers7e989832020-06-19 11:47:21 +01001771(
1772 const std::shared_ptr<OptionDetails>& value,
1773 const std::string& /*name*/,
1774 const std::string& arg
1775)
1776{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001777 auto hash = value->hash();
1778 auto& result = m_parsed[hash];
Jan Eilers7e989832020-06-19 11:47:21 +01001779 result.parse(value, arg);
1780
1781 m_sequential.emplace_back(value->long_name(), arg);
1782}
1783
1784inline
1785void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001786OptionParser::checked_parse_arg
Jan Eilers7e989832020-06-19 11:47:21 +01001787(
1788 int argc,
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001789 const char* const* argv,
Jan Eilers7e989832020-06-19 11:47:21 +01001790 int& current,
1791 const std::shared_ptr<OptionDetails>& value,
1792 const std::string& name
1793)
1794{
1795 if (current + 1 >= argc)
1796 {
1797 if (value->value().has_implicit())
1798 {
1799 parse_option(value, name, value->value().get_implicit_value());
1800 }
1801 else
1802 {
1803 throw_or_mimic<missing_argument_exception>(name);
1804 }
1805 }
1806 else
1807 {
1808 if (value->value().has_implicit())
1809 {
1810 parse_option(value, name, value->value().get_implicit_value());
1811 }
1812 else
1813 {
1814 parse_option(value, name, argv[current + 1]);
1815 ++current;
1816 }
1817 }
1818}
1819
1820inline
1821void
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001822OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
Jan Eilers7e989832020-06-19 11:47:21 +01001823{
Jan Eilers7e989832020-06-19 11:47:21 +01001824 parse_option(iter->second, option, arg);
1825}
1826
1827inline
1828bool
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001829OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
Jan Eilers7e989832020-06-19 11:47:21 +01001830{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001831 while (next != m_positional.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001832 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001833 auto iter = m_options.find(*next);
1834 if (iter != m_options.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001835 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001836 auto& result = m_parsed[iter->second->hash()];
Jan Eilers7e989832020-06-19 11:47:21 +01001837 if (!iter->second->value().is_container())
1838 {
1839 if (result.count() == 0)
1840 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001841 add_to_option(iter, *next, a);
1842 ++next;
Jan Eilers7e989832020-06-19 11:47:21 +01001843 return true;
1844 }
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001845 ++next;
Jan Eilers7e989832020-06-19 11:47:21 +01001846 continue;
1847 }
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001848 add_to_option(iter, *next, a);
Jan Eilers7e989832020-06-19 11:47:21 +01001849 return true;
1850 }
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001851 throw_or_mimic<option_not_exists_exception>(*next);
Jan Eilers7e989832020-06-19 11:47:21 +01001852 }
1853
1854 return false;
1855}
1856
1857inline
1858void
1859Options::parse_positional(std::string option)
1860{
1861 parse_positional(std::vector<std::string>{std::move(option)});
1862}
1863
1864inline
1865void
1866Options::parse_positional(std::vector<std::string> options)
1867{
1868 m_positional = std::move(options);
Jan Eilers7e989832020-06-19 11:47:21 +01001869
1870 m_positional_set.insert(m_positional.begin(), m_positional.end());
1871}
1872
1873inline
1874void
1875Options::parse_positional(std::initializer_list<std::string> options)
1876{
1877 parse_positional(std::vector<std::string>(options));
1878}
1879
1880inline
1881ParseResult
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001882Options::parse(int argc, const char* const* argv)
Jan Eilers7e989832020-06-19 11:47:21 +01001883{
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001884 OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
1885
1886 return parser.parse(argc, argv);
Jan Eilers7e989832020-06-19 11:47:21 +01001887}
1888
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001889inline ParseResult
1890OptionParser::parse(int argc, const char* const* argv)
Jan Eilers7e989832020-06-19 11:47:21 +01001891{
1892 int current = 1;
Jan Eilers7e989832020-06-19 11:47:21 +01001893 bool consume_remaining = false;
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001894 PositionalListIterator next_positional = m_positional.begin();
1895
1896 std::vector<std::string> unmatched;
Jan Eilers7e989832020-06-19 11:47:21 +01001897
1898 while (current != argc)
1899 {
1900 if (strcmp(argv[current], "--") == 0)
1901 {
1902 consume_remaining = true;
1903 ++current;
1904 break;
1905 }
1906
1907 std::match_results<const char*> result;
1908 std::regex_match(argv[current], result, option_matcher);
1909
1910 if (result.empty())
1911 {
1912 //not a flag
1913
1914 // but if it starts with a `-`, then it's an error
1915 if (argv[current][0] == '-' && argv[current][1] != '\0') {
1916 if (!m_allow_unrecognised) {
1917 throw_or_mimic<option_syntax_exception>(argv[current]);
1918 }
1919 }
1920
1921 //if true is returned here then it was consumed, otherwise it is
1922 //ignored
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001923 if (consume_positional(argv[current], next_positional))
Jan Eilers7e989832020-06-19 11:47:21 +01001924 {
1925 }
1926 else
1927 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001928 unmatched.push_back(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01001929 }
1930 //if we return from here then it was parsed successfully, so continue
1931 }
1932 else
1933 {
1934 //short or long option?
1935 if (result[4].length() != 0)
1936 {
1937 const std::string& s = result[4];
1938
1939 for (std::size_t i = 0; i != s.size(); ++i)
1940 {
1941 std::string name(1, s[i]);
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001942 auto iter = m_options.find(name);
Jan Eilers7e989832020-06-19 11:47:21 +01001943
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001944 if (iter == m_options.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001945 {
1946 if (m_allow_unrecognised)
1947 {
1948 continue;
1949 }
1950 //error
1951 throw_or_mimic<option_not_exists_exception>(name);
1952 }
1953
1954 auto value = iter->second;
1955
1956 if (i + 1 == s.size())
1957 {
1958 //it must be the last argument
1959 checked_parse_arg(argc, argv, current, value, name);
1960 }
1961 else if (value->value().has_implicit())
1962 {
1963 parse_option(value, name, value->value().get_implicit_value());
1964 }
1965 else
1966 {
1967 //error
1968 throw_or_mimic<option_requires_argument_exception>(name);
1969 }
1970 }
1971 }
1972 else if (result[1].length() != 0)
1973 {
1974 const std::string& name = result[1];
1975
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001976 auto iter = m_options.find(name);
Jan Eilers7e989832020-06-19 11:47:21 +01001977
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001978 if (iter == m_options.end())
Jan Eilers7e989832020-06-19 11:47:21 +01001979 {
1980 if (m_allow_unrecognised)
1981 {
1982 // keep unrecognised options in argument list, skip to next argument
Matthew Sloyan84dc8432020-10-06 16:06:07 +01001983 unmatched.push_back(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01001984 ++current;
1985 continue;
1986 }
1987 //error
1988 throw_or_mimic<option_not_exists_exception>(name);
1989 }
1990
1991 auto opt = iter->second;
1992
1993 //equals provided for long option?
1994 if (result[2].length() != 0)
1995 {
1996 //parse the option given
1997
1998 parse_option(opt, name, result[3]);
1999 }
2000 else
2001 {
2002 //parse the next argument
2003 checked_parse_arg(argc, argv, current, opt, name);
2004 }
2005 }
2006
2007 }
2008
2009 ++current;
2010 }
2011
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002012 for (auto& opt : m_options)
Jan Eilers7e989832020-06-19 11:47:21 +01002013 {
2014 auto& detail = opt.second;
2015 const auto& value = detail->value();
2016
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002017 auto& store = m_parsed[detail->hash()];
Jan Eilers7e989832020-06-19 11:47:21 +01002018
2019 if(value.has_default() && !store.count() && !store.has_default()){
2020 parse_default(detail);
2021 }
2022 }
2023
2024 if (consume_remaining)
2025 {
2026 while (current < argc)
2027 {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002028 if (!consume_positional(argv[current], next_positional)) {
Jan Eilers7e989832020-06-19 11:47:21 +01002029 break;
2030 }
2031 ++current;
2032 }
2033
2034 //adjust argv for any that couldn't be swallowed
2035 while (current != argc) {
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002036 unmatched.push_back(argv[current]);
Jan Eilers7e989832020-06-19 11:47:21 +01002037 ++current;
2038 }
2039 }
2040
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002041 finalise_aliases();
Jan Eilers7e989832020-06-19 11:47:21 +01002042
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002043 ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched));
2044 return parsed;
2045}
2046
2047inline
2048void
2049OptionParser::finalise_aliases()
2050{
2051 for (auto& option: m_options)
2052 {
2053 auto& detail = *option.second;
2054 auto hash = detail.hash();
2055 m_keys[detail.short_name()] = hash;
2056 m_keys[detail.long_name()] = hash;
2057
2058 m_parsed.emplace(hash, OptionValue());
2059 }
Jan Eilers7e989832020-06-19 11:47:21 +01002060}
2061
2062inline
2063void
2064Options::add_option
2065(
2066 const std::string& group,
2067 const Option& option
2068)
2069{
2070 add_options(group, {option});
2071}
2072
2073inline
2074void
2075Options::add_option
2076(
2077 const std::string& group,
2078 const std::string& s,
2079 const std::string& l,
2080 std::string desc,
2081 const std::shared_ptr<const Value>& value,
2082 std::string arg_help
2083)
2084{
2085 auto stringDesc = toLocalString(std::move(desc));
2086 auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
2087
2088 if (!s.empty())
2089 {
2090 add_one_option(s, option);
2091 }
2092
2093 if (!l.empty())
2094 {
2095 add_one_option(l, option);
2096 }
2097
Matthew Sloyan84dc8432020-10-06 16:06:07 +01002098 m_option_list.push_front(*option.get());
2099 auto iter = m_option_list.begin();
2100 m_option_map[s] = iter;
2101 m_option_map[l] = iter;
2102
Jan Eilers7e989832020-06-19 11:47:21 +01002103 //add the help details
2104 auto& options = m_help[group];
2105
2106 options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
2107 value->has_default(), value->get_default_value(),
2108 value->has_implicit(), value->get_implicit_value(),
2109 std::move(arg_help),
2110 value->is_container(),
2111 value->is_boolean()});
2112}
2113
2114inline
2115void
2116Options::add_one_option
2117(
2118 const std::string& option,
2119 const std::shared_ptr<OptionDetails>& details
2120)
2121{
2122 auto in = m_options->emplace(option, details);
2123
2124 if (!in.second)
2125 {
2126 throw_or_mimic<option_exists_error>(option);
2127 }
2128}
2129
2130inline
2131String
2132Options::help_one_group(const std::string& g) const
2133{
2134 using OptionHelp = std::vector<std::pair<String, String>>;
2135
2136 auto group = m_help.find(g);
2137 if (group == m_help.end())
2138 {
2139 return "";
2140 }
2141
2142 OptionHelp format;
2143
2144 size_t longest = 0;
2145
2146 String result;
2147
2148 if (!g.empty())
2149 {
2150 result += toLocalString(" " + g + " options:\n");
2151 }
2152
2153 for (const auto& o : group->second.options)
2154 {
2155 if (m_positional_set.find(o.l) != m_positional_set.end() &&
2156 !m_show_positional)
2157 {
2158 continue;
2159 }
2160
2161 auto s = format_option(o);
2162 longest = (std::max)(longest, stringLength(s));
2163 format.push_back(std::make_pair(s, String()));
2164 }
2165
2166 longest = (std::min)(longest, static_cast<size_t>(OPTION_LONGEST));
2167
2168 //widest allowed description
2169 auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
2170
2171 auto fiter = format.begin();
2172 for (const auto& o : group->second.options)
2173 {
2174 if (m_positional_set.find(o.l) != m_positional_set.end() &&
2175 !m_show_positional)
2176 {
2177 continue;
2178 }
2179
2180 auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
2181
2182 result += fiter->first;
2183 if (stringLength(fiter->first) > longest)
2184 {
2185 result += '\n';
2186 result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
2187 }
2188 else
2189 {
2190 result += toLocalString(std::string(longest + OPTION_DESC_GAP -
2191 stringLength(fiter->first),
2192 ' '));
2193 }
2194 result += d;
2195 result += '\n';
2196
2197 ++fiter;
2198 }
2199
2200 return result;
2201}
2202
2203inline
2204void
2205Options::generate_group_help
2206(
2207 String& result,
2208 const std::vector<std::string>& print_groups
2209) const
2210{
2211 for (size_t i = 0; i != print_groups.size(); ++i)
2212 {
2213 const String& group_help_text = help_one_group(print_groups[i]);
2214 if (empty(group_help_text))
2215 {
2216 continue;
2217 }
2218 result += group_help_text;
2219 if (i < print_groups.size() - 1)
2220 {
2221 result += '\n';
2222 }
2223 }
2224}
2225
2226inline
2227void
2228Options::generate_all_groups_help(String& result) const
2229{
2230 std::vector<std::string> all_groups;
2231 all_groups.reserve(m_help.size());
2232
2233 for (const auto& group : m_help)
2234 {
2235 all_groups.push_back(group.first);
2236 }
2237
2238 generate_group_help(result, all_groups);
2239}
2240
2241inline
2242std::string
2243Options::help(const std::vector<std::string>& help_groups) const
2244{
2245 String result = m_help_string + "\nUsage:\n " +
2246 toLocalString(m_program) + " " + toLocalString(m_custom_help);
2247
2248 if (!m_positional.empty() && !m_positional_help.empty()) {
2249 result += " " + toLocalString(m_positional_help);
2250 }
2251
2252 result += "\n\n";
2253
2254 if (help_groups.empty())
2255 {
2256 generate_all_groups_help(result);
2257 }
2258 else
2259 {
2260 generate_group_help(result, help_groups);
2261 }
2262
2263 return toUTF8String(result);
2264}
2265
2266inline
2267std::vector<std::string>
2268Options::groups() const
2269{
2270 std::vector<std::string> g;
2271
2272 std::transform(
2273 m_help.begin(),
2274 m_help.end(),
2275 std::back_inserter(g),
2276 [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
2277 {
2278 return pair.first;
2279 }
2280 );
2281
2282 return g;
2283}
2284
2285inline
2286const HelpGroupDetails&
2287Options::group_help(const std::string& group) const
2288{
2289 return m_help.at(group);
2290}
2291
2292} // namespace cxxopts
2293
2294#endif //CXXOPTS_HPP_INCLUDED