1 /*****************************************************************************
2 * ___ _ _ ___ ___
3 * | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++
4 * | |_ | |_ | | | _/ _/ version 1.2.0
5 * |___||___||_| |_| |_| https://github.com/muellan/clipp
6 *
7 * Licensed under the MIT License <http://opensource.org/licenses/MIT>.
8 * Copyright (c) 2017-18 André Müller <foss@andremueller-online.de>
9 *
10 * ---------------------------------------------------------------------------
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 * OTHER DEALINGS IN THE SOFTWARE.
28 *
29 *****************************************************************************/
30
31 #ifndef AM_CLIPP_H__
32 #define AM_CLIPP_H__
33
34 #include <cstring>
35 #include <string>
36 #include <cstdlib>
37 #include <cstring>
38 #include <cctype>
39 #include <memory>
40 #include <vector>
41 #include <limits>
42 #include <stack>
43 #include <algorithm>
44 #include <sstream>
45 #include <utility>
46 #include <iterator>
47 #include <functional>
48
49
50 /*************************************************************************//**
51 *
52 * @brief primary namespace
53 *
54 *****************************************************************************/
55 namespace clipp {
56
57
58
59 /*****************************************************************************
60 *
61 * basic constants and datatype definitions
62 *
63 *****************************************************************************/
64 using arg_index = int;
65
66 using arg_string = std::string;
67 using doc_string = std::string;
68
69 using arg_list = std::vector<arg_string>;
70
71
72
73 /*************************************************************************//**
74 *
75 * @brief tristate
76 *
77 *****************************************************************************/
78 enum class tri : char { no, yes, either };
79
operator ==(tri t,bool b)80 inline constexpr bool operator == (tri t, bool b) noexcept {
81 return b ? t != tri::no : t != tri::yes;
82 }
operator ==(bool b,tri t)83 inline constexpr bool operator == (bool b, tri t) noexcept { return (t == b); }
operator !=(tri t,bool b)84 inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); }
operator !=(bool b,tri t)85 inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); }
86
87
88
89 /*************************************************************************//**
90 *
91 * @brief (start,size) index range
92 *
93 *****************************************************************************/
94 class subrange {
95 public:
96 using size_type = arg_string::size_type;
97
98 /** @brief default: no match */
99 explicit constexpr
subrange()100 subrange() noexcept :
101 at_{arg_string::npos}, length_{0}
102 {}
103
104 /** @brief match length & position within subject string */
105 explicit constexpr
subrange(size_type pos,size_type len)106 subrange(size_type pos, size_type len) noexcept :
107 at_{pos}, length_{len}
108 {}
109
110 /** @brief position of the match within the subject string */
at() const111 constexpr size_type at() const noexcept { return at_; }
112 /** @brief length of the matching subsequence */
length() const113 constexpr size_type length() const noexcept { return length_; }
114
115 /** @brief returns true, if query string is a prefix of the subject string */
prefix() const116 constexpr bool prefix() const noexcept {
117 return at_ == 0 && length_ > 0;
118 }
119
120 /** @brief returns true, if query is a substring of the query string */
operator bool() const121 constexpr explicit operator bool () const noexcept {
122 return at_ != arg_string::npos && length_ > 0;
123 }
124
125 private:
126 size_type at_;
127 size_type length_;
128 };
129
130
131
132 /*************************************************************************//**
133 *
134 * @brief match predicates
135 *
136 *****************************************************************************/
137 using match_predicate = std::function<bool(const arg_string&)>;
138 using match_function = std::function<subrange(const arg_string&)>;
139
140
141
142
143
144
145 /*************************************************************************//**
146 *
147 * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!)
148 * no interface guarantees; might be changed or removed in the future
149 *
150 *****************************************************************************/
151 namespace traits {
152
153 /*************************************************************************//**
154 *
155 * @brief function (class) signature type trait
156 *
157 *****************************************************************************/
158 template<class Fn, class Ret, class... Args>
159 constexpr auto
160 check_is_callable(int) -> decltype(
161 std::declval<Fn>()(std::declval<Args>()...),
162 std::integral_constant<bool,
163 std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} );
164
165 template<class,class,class...>
166 constexpr auto
167 check_is_callable(long) -> std::false_type;
168
169 template<class Fn, class Ret>
170 constexpr auto
171 check_is_callable_without_arg(int) -> decltype(
172 std::declval<Fn>()(),
173 std::integral_constant<bool,
174 std::is_same<Ret,typename std::result_of<Fn()>::type>::value>{} );
175
176 template<class,class>
177 constexpr auto
178 check_is_callable_without_arg(long) -> std::false_type;
179
180
181
182 template<class Fn, class... Args>
183 constexpr auto
184 check_is_void_callable(int) -> decltype(
185 std::declval<Fn>()(std::declval<Args>()...), std::true_type{});
186
187 template<class,class,class...>
188 constexpr auto
189 check_is_void_callable(long) -> std::false_type;
190
191 template<class Fn>
192 constexpr auto
193 check_is_void_callable_without_arg(int) -> decltype(
194 std::declval<Fn>()(), std::true_type{});
195
196 template<class>
197 constexpr auto
198 check_is_void_callable_without_arg(long) -> std::false_type;
199
200
201
202 template<class Fn, class Ret>
203 struct is_callable;
204
205
206 template<class Fn, class Ret, class... Args>
207 struct is_callable<Fn, Ret(Args...)> :
208 decltype(check_is_callable<Fn,Ret,Args...>(0))
209 {};
210
211 template<class Fn, class Ret>
212 struct is_callable<Fn,Ret()> :
213 decltype(check_is_callable_without_arg<Fn,Ret>(0))
214 {};
215
216
217 template<class Fn, class... Args>
218 struct is_callable<Fn, void(Args...)> :
219 decltype(check_is_void_callable<Fn,Args...>(0))
220 {};
221
222 template<class Fn>
223 struct is_callable<Fn,void()> :
224 decltype(check_is_void_callable_without_arg<Fn>(0))
225 {};
226
227
228
229 /*************************************************************************//**
230 *
231 * @brief input range type trait
232 *
233 *****************************************************************************/
234 template<class T>
235 constexpr auto
236 check_is_input_range(int) -> decltype(
237 begin(std::declval<T>()), end(std::declval<T>()),
238 std::true_type{});
239
240 template<class T>
241 constexpr auto
242 check_is_input_range(char) -> decltype(
243 std::begin(std::declval<T>()), std::end(std::declval<T>()),
244 std::true_type{});
245
246 template<class>
247 constexpr auto
248 check_is_input_range(long) -> std::false_type;
249
250 template<class T>
251 struct is_input_range :
252 decltype(check_is_input_range<T>(0))
253 {};
254
255
256
257 /*************************************************************************//**
258 *
259 * @brief size() member type trait
260 *
261 *****************************************************************************/
262 template<class T>
263 constexpr auto
264 check_has_size_getter(int) ->
265 decltype(std::declval<T>().size(), std::true_type{});
266
267 template<class>
268 constexpr auto
269 check_has_size_getter(long) -> std::false_type;
270
271 template<class T>
272 struct has_size_getter :
273 decltype(check_has_size_getter<T>(0))
274 {};
275
276 } // namespace traits
277
278
279
280
281
282
283 /*************************************************************************//**
284 *
285 * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
286 * no interface guarantees; might be changed or removed in the future
287 *
288 *****************************************************************************/
289 namespace detail {
290
291
292 /*************************************************************************//**
293 * @brief forwards string to first non-whitespace char;
294 * std string -> unsigned conv yields max value, but we want 0;
295 * also checks for nullptr
296 *****************************************************************************/
297 inline bool
fwd_to_unsigned_int(const char * & s)298 fwd_to_unsigned_int(const char*& s)
299 {
300 if(!s) return false;
301 for(; std::isspace(*s); ++s);
302 if(!s[0] || s[0] == '-') return false;
303 if(s[0] == '-') return false;
304 return true;
305 }
306
307
308 /*************************************************************************//**
309 *
310 * @brief value limits clamping
311 *
312 *****************************************************************************/
313 template<class T, class V, bool = (sizeof(V) > sizeof(T))>
314 struct limits_clamped {
fromclipp::detail::limits_clamped315 static T from(const V& v) {
316 if(v >= V(std::numeric_limits<T>::max())) {
317 return std::numeric_limits<T>::max();
318 }
319 if(v <= V(std::numeric_limits<T>::lowest())) {
320 return std::numeric_limits<T>::lowest();
321 }
322 return T(v);
323 }
324 };
325
326 template<class T, class V>
327 struct limits_clamped<T,V,false> {
fromclipp::detail::limits_clamped328 static T from(const V& v) { return T(v); }
329 };
330
331
332 /*************************************************************************//**
333 *
334 * @brief returns value of v as a T, clamped at T's maximum
335 *
336 *****************************************************************************/
337 template<class T, class V>
clamped_on_limits(const V & v)338 inline T clamped_on_limits(const V& v) {
339 return limits_clamped<T,V>::from(v);
340 }
341
342
343
344
345 /*************************************************************************//**
346 *
347 * @brief type conversion helpers
348 *
349 *****************************************************************************/
350 template<class T>
351 struct make {
fromclipp::detail::make352 static inline T from(const char* s) {
353 if(!s) return false;
354 //a conversion from const char* to / must exist
355 return static_cast<T>(s);
356 }
357 };
358
359 template<>
360 struct make<bool> {
fromclipp::detail::make361 static inline bool from(const char* s) {
362 if(!s) return false;
363 return static_cast<bool>(s);
364 }
365 };
366
367 template<>
368 struct make<unsigned char> {
fromclipp::detail::make369 static inline unsigned char from(const char* s) {
370 if(!fwd_to_unsigned_int(s)) return (0);
371 return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10));
372 }
373 };
374
375 template<>
376 struct make<unsigned short int> {
fromclipp::detail::make377 static inline unsigned short int from(const char* s) {
378 if(!fwd_to_unsigned_int(s)) return (0);
379 return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10));
380 }
381 };
382
383 template<>
384 struct make<unsigned int> {
fromclipp::detail::make385 static inline unsigned int from(const char* s) {
386 if(!fwd_to_unsigned_int(s)) return (0);
387 return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10));
388 }
389 };
390
391 template<>
392 struct make<unsigned long int> {
fromclipp::detail::make393 static inline unsigned long int from(const char* s) {
394 if(!fwd_to_unsigned_int(s)) return (0);
395 return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10));
396 }
397 };
398
399 template<>
400 struct make<unsigned long long int> {
fromclipp::detail::make401 static inline unsigned long long int from(const char* s) {
402 if(!fwd_to_unsigned_int(s)) return (0);
403 return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10));
404 }
405 };
406
407 template<>
408 struct make<char> {
fromclipp::detail::make409 static inline char from(const char* s) {
410 //parse as single character?
411 const auto n = std::strlen(s);
412 if(n == 1) return s[0];
413 //parse as integer
414 return clamped_on_limits<char>(std::strtoll(s,nullptr,10));
415 }
416 };
417
418 template<>
419 struct make<short int> {
fromclipp::detail::make420 static inline short int from(const char* s) {
421 return clamped_on_limits<short int>(std::strtoll(s,nullptr,10));
422 }
423 };
424
425 template<>
426 struct make<int> {
fromclipp::detail::make427 static inline int from(const char* s) {
428 return clamped_on_limits<int>(std::strtoll(s,nullptr,10));
429 }
430 };
431
432 template<>
433 struct make<long int> {
fromclipp::detail::make434 static inline long int from(const char* s) {
435 return clamped_on_limits<long int>(std::strtoll(s,nullptr,10));
436 }
437 };
438
439 template<>
440 struct make<long long int> {
fromclipp::detail::make441 static inline long long int from(const char* s) {
442 return (std::strtoll(s,nullptr,10));
443 }
444 };
445
446 template<>
447 struct make<float> {
fromclipp::detail::make448 static inline float from(const char* s) {
449 return (std::strtof(s,nullptr));
450 }
451 };
452
453 template<>
454 struct make<double> {
fromclipp::detail::make455 static inline double from(const char* s) {
456 return (std::strtod(s,nullptr));
457 }
458 };
459
460 template<>
461 struct make<long double> {
fromclipp::detail::make462 static inline long double from(const char* s) {
463 return (std::strtold(s,nullptr));
464 }
465 };
466
467 template<>
468 struct make<std::string> {
fromclipp::detail::make469 static inline std::string from(const char* s) {
470 return std::string(s);
471 }
472 };
473
474
475
476 /*************************************************************************//**
477 *
478 * @brief assigns boolean constant to one or multiple target objects
479 *
480 *****************************************************************************/
481 template<class T, class V = T>
482 class assign_value
483 {
484 public:
485 template<class X>
486 explicit constexpr
assign_value(T & target,X && value)487 assign_value(T& target, X&& value) noexcept :
488 t_{std::addressof(target)}, v_{std::forward<X>(value)}
489 {}
490
operator ()() const491 void operator () () const {
492 if(t_) *t_ = v_;
493 }
494
495 private:
496 T* t_;
497 V v_;
498 };
499
500
501
502 /*************************************************************************//**
503 *
504 * @brief flips bools
505 *
506 *****************************************************************************/
507 class flip_bool
508 {
509 public:
510 explicit constexpr
flip_bool(bool & target)511 flip_bool(bool& target) noexcept :
512 b_{&target}
513 {}
514
operator ()() const515 void operator () () const {
516 if(b_) *b_ = !*b_;
517 }
518
519 private:
520 bool* b_;
521 };
522
523
524
525 /*************************************************************************//**
526 *
527 * @brief increments using operator ++
528 *
529 *****************************************************************************/
530 template<class T>
531 class increment
532 {
533 public:
534 explicit constexpr
increment(T & target)535 increment(T& target) noexcept : t_{std::addressof(target)} {}
536
operator ()() const537 void operator () () const {
538 if(t_) ++(*t_);
539 }
540
541 private:
542 T* t_;
543 };
544
545
546
547 /*************************************************************************//**
548 *
549 * @brief decrements using operator --
550 *
551 *****************************************************************************/
552 template<class T>
553 class decrement
554 {
555 public:
556 explicit constexpr
decrement(T & target)557 decrement(T& target) noexcept : t_{std::addressof(target)} {}
558
operator ()() const559 void operator () () const {
560 if(t_) --(*t_);
561 }
562
563 private:
564 T* t_;
565 };
566
567
568
569 /*************************************************************************//**
570 *
571 * @brief increments by a fixed amount using operator +=
572 *
573 *****************************************************************************/
574 template<class T>
575 class increment_by
576 {
577 public:
578 explicit constexpr
increment_by(T & target,T by)579 increment_by(T& target, T by) noexcept :
580 t_{std::addressof(target)}, by_{std::move(by)}
581 {}
582
operator ()() const583 void operator () () const {
584 if(t_) (*t_) += by_;
585 }
586
587 private:
588 T* t_;
589 T by_;
590 };
591
592
593
594
595 /*************************************************************************//**
596 *
597 * @brief makes a value from a string and assigns it to an object
598 *
599 *****************************************************************************/
600 template<class T>
601 class map_arg_to
602 {
603 public:
604 explicit constexpr
map_arg_to(T & target)605 map_arg_to(T& target) noexcept : t_{std::addressof(target)} {}
606
operator ()(const char * s) const607 void operator () (const char* s) const {
608 if(t_ && s && (std::strlen(s) > 0))
609 *t_ = detail::make<T>::from(s);
610 }
611
612 private:
613 T* t_;
614 };
615
616
617 //-------------------------------------------------------------------
618 /**
619 * @brief specialization for vectors: append element
620 */
621 template<class T>
622 class map_arg_to<std::vector<T>>
623 {
624 public:
map_arg_to(std::vector<T> & target)625 map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {}
626
operator ()(const char * s) const627 void operator () (const char* s) const {
628 if(t_ && s) t_->push_back(detail::make<T>::from(s));
629 }
630
631 private:
632 std::vector<T>* t_;
633 };
634
635
636 //-------------------------------------------------------------------
637 /**
638 * @brief specialization for bools:
639 * set to true regardless of string content
640 */
641 template<>
642 class map_arg_to<bool>
643 {
644 public:
map_arg_to(bool & target)645 map_arg_to(bool& target): t_{&target} {}
646
operator ()(const char * s) const647 void operator () (const char* s) const {
648 if(t_ && s) *t_ = true;
649 }
650
651 private:
652 bool* t_;
653 };
654
655
656 } // namespace detail
657
658
659
660
661
662
663 /*************************************************************************//**
664 *
665 * @brief string matching and processing tools
666 *
667 *****************************************************************************/
668
669 namespace str {
670
671
672 /*************************************************************************//**
673 *
674 * @brief converts string to value of target type 'T'
675 *
676 *****************************************************************************/
677 template<class T>
make(const arg_string & s)678 T make(const arg_string& s)
679 {
680 return detail::make<T>::from(s);
681 }
682
683
684
685 /*************************************************************************//**
686 *
687 * @brief removes trailing whitespace from string
688 *
689 *****************************************************************************/
690 template<class C, class T, class A>
691 inline void
trimr(std::basic_string<C,T,A> & s)692 trimr(std::basic_string<C,T,A>& s)
693 {
694 if(s.empty()) return;
695
696 s.erase(
697 std::find_if_not(s.rbegin(), s.rend(),
698 [](char c) { return std::isspace(c);} ).base(),
699 s.end() );
700 }
701
702
703 /*************************************************************************//**
704 *
705 * @brief removes leading whitespace from string
706 *
707 *****************************************************************************/
708 template<class C, class T, class A>
709 inline void
triml(std::basic_string<C,T,A> & s)710 triml(std::basic_string<C,T,A>& s)
711 {
712 if(s.empty()) return;
713
714 s.erase(
715 s.begin(),
716 std::find_if_not(s.begin(), s.end(),
717 [](char c) { return std::isspace(c);})
718 );
719 }
720
721
722 /*************************************************************************//**
723 *
724 * @brief removes leading and trailing whitespace from string
725 *
726 *****************************************************************************/
727 template<class C, class T, class A>
728 inline void
trim(std::basic_string<C,T,A> & s)729 trim(std::basic_string<C,T,A>& s)
730 {
731 triml(s);
732 trimr(s);
733 }
734
735
736 /*************************************************************************//**
737 *
738 * @brief removes all whitespaces from string
739 *
740 *****************************************************************************/
741 template<class C, class T, class A>
742 inline void
remove_ws(std::basic_string<C,T,A> & s)743 remove_ws(std::basic_string<C,T,A>& s)
744 {
745 if(s.empty()) return;
746
747 s.erase(std::remove_if(s.begin(), s.end(),
748 [](char c) { return std::isspace(c); }),
749 s.end() );
750 }
751
752
753 /*************************************************************************//**
754 *
755 * @brief returns true, if the 'prefix' argument
756 * is a prefix of the 'subject' argument
757 *
758 *****************************************************************************/
759 template<class C, class T, class A>
760 inline bool
has_prefix(const std::basic_string<C,T,A> & subject,const std::basic_string<C,T,A> & prefix)761 has_prefix(const std::basic_string<C,T,A>& subject,
762 const std::basic_string<C,T,A>& prefix)
763 {
764 if(prefix.size() > subject.size()) return false;
765 return subject.find(prefix) == 0;
766 }
767
768
769 /*************************************************************************//**
770 *
771 * @brief returns true, if the 'postfix' argument
772 * is a postfix of the 'subject' argument
773 *
774 *****************************************************************************/
775 template<class C, class T, class A>
776 inline bool
has_postfix(const std::basic_string<C,T,A> & subject,const std::basic_string<C,T,A> & postfix)777 has_postfix(const std::basic_string<C,T,A>& subject,
778 const std::basic_string<C,T,A>& postfix)
779 {
780 if(postfix.size() > subject.size()) return false;
781 return (subject.size() - postfix.size()) == subject.find(postfix);
782 }
783
784
785
786 /*************************************************************************//**
787 *
788 * @brief returns longest common prefix of several
789 * sequential random access containers
790 *
791 * @details InputRange require begin and end (member functions or overloads)
792 * the elements of InputRange require a size() member
793 *
794 *****************************************************************************/
795 template<class InputRange>
796 auto
longest_common_prefix(const InputRange & strs)797 longest_common_prefix(const InputRange& strs)
798 -> typename std::decay<decltype(*begin(strs))>::type
799 {
800 static_assert(traits::is_input_range<InputRange>(),
801 "parameter must satisfy the InputRange concept");
802
803 static_assert(traits::has_size_getter<
804 typename std::decay<decltype(*begin(strs))>::type>(),
805 "elements of input range must have a ::size() member function");
806
807 using std::begin;
808 using std::end;
809
810 using item_t = typename std::decay<decltype(*begin(strs))>::type;
811 using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type;
812
813 const auto n = size_t(distance(begin(strs), end(strs)));
814 if(n < 1) return item_t("");
815 if(n == 1) return *begin(strs);
816
817 //length of shortest string
818 auto m = std::min_element(begin(strs), end(strs),
819 [](const item_t& a, const item_t& b) {
820 return a.size() < b.size(); })->size();
821
822 //check each character until we find a mismatch
823 for(str_size_t i = 0; i < m; ++i) {
824 for(str_size_t j = 1; j < n; ++j) {
825 if(strs[j][i] != strs[j-1][i])
826 return strs[0].substr(0, i);
827 }
828 }
829 return strs[0].substr(0, m);
830 }
831
832
833
834 /*************************************************************************//**
835 *
836 * @brief returns longest substring range that could be found in 'arg'
837 *
838 * @param arg string to be searched in
839 * @param substrings range of candidate substrings
840 *
841 *****************************************************************************/
842 template<class C, class T, class A, class InputRange>
843 subrange
longest_substring_match(const std::basic_string<C,T,A> & arg,const InputRange & substrings)844 longest_substring_match(const std::basic_string<C,T,A>& arg,
845 const InputRange& substrings)
846 {
847 using string_t = std::basic_string<C,T,A>;
848
849 static_assert(traits::is_input_range<InputRange>(),
850 "parameter must satisfy the InputRange concept");
851
852 static_assert(std::is_same<string_t,
853 typename std::decay<decltype(*begin(substrings))>::type>(),
854 "substrings must have same type as 'arg'");
855
856 auto i = string_t::npos;
857 auto n = string_t::size_type(0);
858 for(const auto& s : substrings) {
859 auto j = arg.find(s);
860 if(j != string_t::npos && s.size() > n) {
861 i = j;
862 n = s.size();
863 }
864 }
865 return subrange{i,n};
866 }
867
868
869
870 /*************************************************************************//**
871 *
872 * @brief returns longest prefix range that could be found in 'arg'
873 *
874 * @param arg string to be searched in
875 * @param prefixes range of candidate prefix strings
876 *
877 *****************************************************************************/
878 template<class C, class T, class A, class InputRange>
879 subrange
longest_prefix_match(const std::basic_string<C,T,A> & arg,const InputRange & prefixes)880 longest_prefix_match(const std::basic_string<C,T,A>& arg,
881 const InputRange& prefixes)
882 {
883 using string_t = std::basic_string<C,T,A>;
884 using s_size_t = typename string_t::size_type;
885
886 static_assert(traits::is_input_range<InputRange>(),
887 "parameter must satisfy the InputRange concept");
888
889 static_assert(std::is_same<string_t,
890 typename std::decay<decltype(*begin(prefixes))>::type>(),
891 "prefixes must have same type as 'arg'");
892
893 auto i = string_t::npos;
894 auto n = s_size_t(0);
895 for(const auto& s : prefixes) {
896 auto j = arg.find(s);
897 if(j == 0 && s.size() > n) {
898 i = 0;
899 n = s.size();
900 }
901 }
902 return subrange{i,n};
903 }
904
905
906
907 /*************************************************************************//**
908 *
909 * @brief returns the first occurrence of 'query' within 'subject'
910 *
911 *****************************************************************************/
912 template<class C, class T, class A>
913 inline subrange
substring_match(const std::basic_string<C,T,A> & subject,const std::basic_string<C,T,A> & query)914 substring_match(const std::basic_string<C,T,A>& subject,
915 const std::basic_string<C,T,A>& query)
916 {
917 if(subject.empty() || query.empty()) return subrange{};
918 auto i = subject.find(query);
919 if(i == std::basic_string<C,T,A>::npos) return subrange{};
920 return subrange{i,query.size()};
921 }
922
923
924
925 /*************************************************************************//**
926 *
927 * @brief returns first substring match (pos,len) within the input string
928 * that represents a number
929 * (with at maximum one decimal point and digit separators)
930 *
931 *****************************************************************************/
932 template<class C, class T, class A>
933 subrange
first_number_match(std::basic_string<C,T,A> s,C digitSeparator=C (','),C decimalPoint=C ('.'),C exponential=C ('e'))934 first_number_match(std::basic_string<C,T,A> s,
935 C digitSeparator = C(','),
936 C decimalPoint = C('.'),
937 C exponential = C('e'))
938 {
939 using string_t = std::basic_string<C,T,A>;
940
941 str::trim(s);
942 if(s.empty()) return subrange{};
943
944 auto i = s.find_first_of("0123456789+-");
945 if(i == string_t::npos) {
946 i = s.find(decimalPoint);
947 if(i == string_t::npos) return subrange{};
948 }
949
950 bool point = false;
951 bool sep = false;
952 auto exp = string_t::npos;
953 auto j = i + 1;
954 for(; j < s.size(); ++j) {
955 if(s[j] == digitSeparator) {
956 if(!sep) sep = true; else break;
957 }
958 else {
959 sep = false;
960 if(s[j] == decimalPoint) {
961 //only one decimal point before exponent allowed
962 if(!point && exp == string_t::npos) point = true; else break;
963 }
964 else if(std::tolower(s[j]) == std::tolower(exponential)) {
965 //only one exponent separator allowed
966 if(exp == string_t::npos) exp = j; else break;
967 }
968 else if(exp != string_t::npos && (exp+1) == j) {
969 //only sign or digit after exponent separator
970 if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break;
971 }
972 else if(!std::isdigit(s[j])) {
973 break;
974 }
975 }
976 }
977
978 //if length == 1 then must be a digit
979 if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
980
981 return subrange{i,j-i};
982 }
983
984
985
986 /*************************************************************************//**
987 *
988 * @brief returns first substring match (pos,len)
989 * that represents an integer (with optional digit separators)
990 *
991 *****************************************************************************/
992 template<class C, class T, class A>
993 subrange
first_integer_match(std::basic_string<C,T,A> s,C digitSeparator=C (','))994 first_integer_match(std::basic_string<C,T,A> s,
995 C digitSeparator = C(','))
996 {
997 using string_t = std::basic_string<C,T,A>;
998
999 str::trim(s);
1000 if(s.empty()) return subrange{};
1001
1002 auto i = s.find_first_of("0123456789+-");
1003 if(i == string_t::npos) return subrange{};
1004
1005 bool sep = false;
1006 auto j = i + 1;
1007 for(; j < s.size(); ++j) {
1008 if(s[j] == digitSeparator) {
1009 if(!sep) sep = true; else break;
1010 }
1011 else {
1012 sep = false;
1013 if(!std::isdigit(s[j])) break;
1014 }
1015 }
1016
1017 //if length == 1 then must be a digit
1018 if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
1019
1020 return subrange{i,j-i};
1021 }
1022
1023
1024
1025 /*************************************************************************//**
1026 *
1027 * @brief returns true if candidate string represents a number
1028 *
1029 *****************************************************************************/
1030 template<class C, class T, class A>
represents_number(const std::basic_string<C,T,A> & candidate,C digitSeparator=C (','),C decimalPoint=C ('.'),C exponential=C ('e'))1031 bool represents_number(const std::basic_string<C,T,A>& candidate,
1032 C digitSeparator = C(','),
1033 C decimalPoint = C('.'),
1034 C exponential = C('e'))
1035 {
1036 const auto match = str::first_number_match(candidate, digitSeparator,
1037 decimalPoint, exponential);
1038
1039 return (match && match.length() == candidate.size());
1040 }
1041
1042
1043
1044 /*************************************************************************//**
1045 *
1046 * @brief returns true if candidate string represents an integer
1047 *
1048 *****************************************************************************/
1049 template<class C, class T, class A>
represents_integer(const std::basic_string<C,T,A> & candidate,C digitSeparator=C (','))1050 bool represents_integer(const std::basic_string<C,T,A>& candidate,
1051 C digitSeparator = C(','))
1052 {
1053 const auto match = str::first_integer_match(candidate, digitSeparator);
1054 return (match && match.length() == candidate.size());
1055 }
1056
1057 } // namespace str
1058
1059
1060
1061
1062
1063
1064 /*************************************************************************//**
1065 *
1066 * @brief makes function object with a const char* parameter
1067 * that assigns a value to a ref-captured object
1068 *
1069 *****************************************************************************/
1070 template<class T, class V>
1071 inline detail::assign_value<T,V>
set(T & target,V value)1072 set(T& target, V value) {
1073 return detail::assign_value<T>{target, std::move(value)};
1074 }
1075
1076
1077
1078 /*************************************************************************//**
1079 *
1080 * @brief makes parameter-less function object
1081 * that assigns value(s) to a ref-captured object;
1082 * value(s) are obtained by converting the const char* argument to
1083 * the captured object types;
1084 * bools are always set to true if the argument is not nullptr
1085 *
1086 *****************************************************************************/
1087 template<class T>
1088 inline detail::map_arg_to<T>
set(T & target)1089 set(T& target) {
1090 return detail::map_arg_to<T>{target};
1091 }
1092
1093
1094
1095 /*************************************************************************//**
1096 *
1097 * @brief makes function object that sets a bool to true
1098 *
1099 *****************************************************************************/
1100 inline detail::assign_value<bool>
set(bool & target)1101 set(bool& target) {
1102 return detail::assign_value<bool>{target,true};
1103 }
1104
1105 /*************************************************************************//**
1106 *
1107 * @brief makes function object that sets a bool to false
1108 *
1109 *****************************************************************************/
1110 inline detail::assign_value<bool>
unset(bool & target)1111 unset(bool& target) {
1112 return detail::assign_value<bool>{target,false};
1113 }
1114
1115 /*************************************************************************//**
1116 *
1117 * @brief makes function object that flips the value of a ref-captured bool
1118 *
1119 *****************************************************************************/
1120 inline detail::flip_bool
flip(bool & b)1121 flip(bool& b) {
1122 return detail::flip_bool(b);
1123 }
1124
1125
1126
1127
1128
1129 /*************************************************************************//**
1130 *
1131 * @brief makes function object that increments using operator ++
1132 *
1133 *****************************************************************************/
1134 template<class T>
1135 inline detail::increment<T>
increment(T & target)1136 increment(T& target) {
1137 return detail::increment<T>{target};
1138 }
1139
1140 /*************************************************************************//**
1141 *
1142 * @brief makes function object that decrements using operator --
1143 *
1144 *****************************************************************************/
1145 template<class T>
1146 inline detail::increment_by<T>
increment(T & target,T by)1147 increment(T& target, T by) {
1148 return detail::increment_by<T>{target, std::move(by)};
1149 }
1150
1151 /*************************************************************************//**
1152 *
1153 * @brief makes function object that increments by a fixed amount using operator +=
1154 *
1155 *****************************************************************************/
1156 template<class T>
1157 inline detail::decrement<T>
decrement(T & target)1158 decrement(T& target) {
1159 return detail::decrement<T>{target};
1160 }
1161
1162
1163
1164
1165
1166
1167 /*************************************************************************//**
1168 *
1169 * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
1170 *
1171 *****************************************************************************/
1172 namespace detail {
1173
1174
1175 /*************************************************************************//**
1176 *
1177 * @brief mixin that provides action definition and execution
1178 *
1179 *****************************************************************************/
1180 template<class Derived>
1181 class action_provider
1182 {
1183 private:
1184 //---------------------------------------------------------------
1185 using simple_action = std::function<void()>;
1186 using arg_action = std::function<void(const char*)>;
1187 using index_action = std::function<void(int)>;
1188
1189 //-----------------------------------------------------
1190 class simple_action_adapter {
1191 public:
1192 simple_action_adapter() = default;
simple_action_adapter(const simple_action & a)1193 simple_action_adapter(const simple_action& a): action_(a) {}
simple_action_adapter(simple_action && a)1194 simple_action_adapter(simple_action&& a): action_(std::move(a)) {}
operator ()(const char *) const1195 void operator() (const char*) const { action_(); }
operator ()(int) const1196 void operator() (int) const { action_(); }
1197 private:
1198 simple_action action_;
1199 };
1200
1201
1202 public:
1203 //---------------------------------------------------------------
1204 /** @brief adds an action that has an operator() that is callable
1205 * with a 'const char*' argument */
1206 Derived&
call(arg_action a)1207 call(arg_action a) {
1208 argActions_.push_back(std::move(a));
1209 return *static_cast<Derived*>(this);
1210 }
1211
1212 /** @brief adds an action that has an operator()() */
1213 Derived&
call(simple_action a)1214 call(simple_action a) {
1215 argActions_.push_back(simple_action_adapter(std::move(a)));
1216 return *static_cast<Derived*>(this);
1217 }
1218
1219 /** @brief adds an action that has an operator() that is callable
1220 * with a 'const char*' argument */
operator ()(arg_action a)1221 Derived& operator () (arg_action a) { return call(std::move(a)); }
1222
1223 /** @brief adds an action that has an operator()() */
operator ()(simple_action a)1224 Derived& operator () (simple_action a) { return call(std::move(a)); }
1225
1226
1227 //---------------------------------------------------------------
1228 /** @brief adds an action that will set the value of 't' from
1229 * a 'const char*' arg */
1230 template<class Target>
1231 Derived&
set(Target & t)1232 set(Target& t) {
1233 static_assert(!std::is_pointer<Target>::value,
1234 "parameter target type must not be a pointer");
1235
1236 return call(clipp::set(t));
1237 }
1238
1239 /** @brief adds an action that will set the value of 't' to 'v' */
1240 template<class Target, class Value>
1241 Derived&
set(Target & t,Value && v)1242 set(Target& t, Value&& v) {
1243 return call(clipp::set(t, std::forward<Value>(v)));
1244 }
1245
1246
1247 //---------------------------------------------------------------
1248 /** @brief adds an action that will be called if a parameter
1249 * matches an argument for the 2nd, 3rd, 4th, ... time
1250 */
1251 Derived&
if_repeated(simple_action a)1252 if_repeated(simple_action a) {
1253 repeatActions_.push_back(simple_action_adapter{std::move(a)});
1254 return *static_cast<Derived*>(this);
1255 }
1256 /** @brief adds an action that will be called with the argument's
1257 * index if a parameter matches an argument for
1258 * the 2nd, 3rd, 4th, ... time
1259 */
1260 Derived&
if_repeated(index_action a)1261 if_repeated(index_action a) {
1262 repeatActions_.push_back(std::move(a));
1263 return *static_cast<Derived*>(this);
1264 }
1265
1266
1267 //---------------------------------------------------------------
1268 /** @brief adds an action that will be called if a required parameter
1269 * is missing
1270 */
1271 Derived&
if_missing(simple_action a)1272 if_missing(simple_action a) {
1273 missingActions_.push_back(simple_action_adapter{std::move(a)});
1274 return *static_cast<Derived*>(this);
1275 }
1276 /** @brief adds an action that will be called if a required parameter
1277 * is missing; the action will get called with the index of
1278 * the command line argument where the missing event occured first
1279 */
1280 Derived&
if_missing(index_action a)1281 if_missing(index_action a) {
1282 missingActions_.push_back(std::move(a));
1283 return *static_cast<Derived*>(this);
1284 }
1285
1286
1287 //---------------------------------------------------------------
1288 /** @brief adds an action that will be called if a parameter
1289 * was matched, but was unreachable in the current scope
1290 */
1291 Derived&
if_blocked(simple_action a)1292 if_blocked(simple_action a) {
1293 blockedActions_.push_back(simple_action_adapter{std::move(a)});
1294 return *static_cast<Derived*>(this);
1295 }
1296 /** @brief adds an action that will be called if a parameter
1297 * was matched, but was unreachable in the current scope;
1298 * the action will be called with the index of
1299 * the command line argument where the problem occured
1300 */
1301 Derived&
if_blocked(index_action a)1302 if_blocked(index_action a) {
1303 blockedActions_.push_back(std::move(a));
1304 return *static_cast<Derived*>(this);
1305 }
1306
1307
1308 //---------------------------------------------------------------
1309 /** @brief adds an action that will be called if a parameter match
1310 * was in conflict with a different alternative parameter
1311 */
1312 Derived&
if_conflicted(simple_action a)1313 if_conflicted(simple_action a) {
1314 conflictActions_.push_back(simple_action_adapter{std::move(a)});
1315 return *static_cast<Derived*>(this);
1316 }
1317 /** @brief adds an action that will be called if a parameter match
1318 * was in conflict with a different alternative paramete;
1319 * the action will be called with the index of
1320 * the command line argument where the problem occuredr
1321 */
1322 Derived&
if_conflicted(index_action a)1323 if_conflicted(index_action a) {
1324 conflictActions_.push_back(std::move(a));
1325 return *static_cast<Derived*>(this);
1326 }
1327
1328
1329 //---------------------------------------------------------------
1330 /** @brief adds targets = either objects whose values should be
1331 * set by command line arguments or actions that should
1332 * be called in case of a match */
1333 template<class T, class... Ts>
1334 Derived&
target(T && t,Ts &&...ts)1335 target(T&& t, Ts&&... ts) {
1336 target(std::forward<T>(t));
1337 target(std::forward<Ts>(ts)...);
1338 return *static_cast<Derived*>(this);
1339 }
1340
1341 /** @brief adds action that should be called in case of a match */
1342 template<class T, class = typename std::enable_if<
1343 !std::is_fundamental<typename std::decay<T>::type>() &&
1344 (traits::is_callable<T,void()>() ||
1345 traits::is_callable<T,void(const char*)>() )
1346 >::type>
1347 Derived&
1348 target(T&& t) {
1349 call(std::forward<T>(t));
1350 return *static_cast<Derived*>(this);
1351 }
1352
1353 /** @brief adds object whose value should be set by command line arguments
1354 */
1355 template<class T, class = typename std::enable_if<
1356 std::is_fundamental<typename std::decay<T>::type>() ||
1357 (!traits::is_callable<T,void()>() &&
1358 !traits::is_callable<T,void(const char*)>() )
1359 >::type>
1360 Derived&
1361 target(T& t) {
1362 set(t);
1363 return *static_cast<Derived*>(this);
1364 }
1365
1366 //TODO remove ugly empty param list overload
1367 Derived&
target()1368 target() {
1369 return *static_cast<Derived*>(this);
1370 }
1371
1372
1373 //---------------------------------------------------------------
1374 /** @brief adds target, see member function 'target' */
1375 template<class Target>
1376 inline friend Derived&
operator <<(Target && t,Derived & p)1377 operator << (Target&& t, Derived& p) {
1378 p.target(std::forward<Target>(t));
1379 return p;
1380 }
1381 /** @brief adds target, see member function 'target' */
1382 template<class Target>
1383 inline friend Derived&&
operator <<(Target && t,Derived && p)1384 operator << (Target&& t, Derived&& p) {
1385 p.target(std::forward<Target>(t));
1386 return std::move(p);
1387 }
1388
1389 //-----------------------------------------------------
1390 /** @brief adds target, see member function 'target' */
1391 template<class Target>
1392 inline friend Derived&
operator >>(Derived & p,Target && t)1393 operator >> (Derived& p, Target&& t) {
1394 p.target(std::forward<Target>(t));
1395 return p;
1396 }
1397 /** @brief adds target, see member function 'target' */
1398 template<class Target>
1399 inline friend Derived&&
operator >>(Derived && p,Target && t)1400 operator >> (Derived&& p, Target&& t) {
1401 p.target(std::forward<Target>(t));
1402 return std::move(p);
1403 }
1404
1405
1406 //---------------------------------------------------------------
1407 /** @brief executes all argument actions */
execute_actions(const arg_string & arg) const1408 void execute_actions(const arg_string& arg) const {
1409 int i = 0;
1410 for(const auto& a : argActions_) {
1411 ++i;
1412 a(arg.c_str());
1413 }
1414 }
1415
1416 /** @brief executes repeat actions */
notify_repeated(arg_index idx) const1417 void notify_repeated(arg_index idx) const {
1418 for(const auto& a : repeatActions_) a(idx);
1419 }
1420 /** @brief executes missing error actions */
notify_missing(arg_index idx) const1421 void notify_missing(arg_index idx) const {
1422 for(const auto& a : missingActions_) a(idx);
1423 }
1424 /** @brief executes blocked error actions */
notify_blocked(arg_index idx) const1425 void notify_blocked(arg_index idx) const {
1426 for(const auto& a : blockedActions_) a(idx);
1427 }
1428 /** @brief executes conflict error actions */
notify_conflict(arg_index idx) const1429 void notify_conflict(arg_index idx) const {
1430 for(const auto& a : conflictActions_) a(idx);
1431 }
1432
1433 private:
1434 //---------------------------------------------------------------
1435 std::vector<arg_action> argActions_;
1436 std::vector<index_action> repeatActions_;
1437 std::vector<index_action> missingActions_;
1438 std::vector<index_action> blockedActions_;
1439 std::vector<index_action> conflictActions_;
1440 };
1441
1442
1443
1444
1445
1446
1447 /*************************************************************************//**
1448 *
1449 * @brief mixin that provides basic common settings of parameters and groups
1450 *
1451 *****************************************************************************/
1452 template<class Derived>
1453 class token
1454 {
1455 public:
1456 //---------------------------------------------------------------
1457 using doc_string = clipp::doc_string;
1458
1459
1460 //---------------------------------------------------------------
1461 /** @brief returns documentation string */
doc() const1462 const doc_string& doc() const noexcept {
1463 return doc_;
1464 }
1465
1466 /** @brief sets documentations string */
doc(const doc_string & txt)1467 Derived& doc(const doc_string& txt) {
1468 doc_ = txt;
1469 return *static_cast<Derived*>(this);
1470 }
1471
1472 /** @brief sets documentations string */
doc(doc_string && txt)1473 Derived& doc(doc_string&& txt) {
1474 doc_ = std::move(txt);
1475 return *static_cast<Derived*>(this);
1476 }
1477
1478
1479 //---------------------------------------------------------------
1480 /** @brief returns if a group/parameter is repeatable */
repeatable() const1481 bool repeatable() const noexcept {
1482 return repeatable_;
1483 }
1484
1485 /** @brief sets repeatability of group/parameter */
repeatable(bool yes)1486 Derived& repeatable(bool yes) noexcept {
1487 repeatable_ = yes;
1488 return *static_cast<Derived*>(this);
1489 }
1490
1491
1492 //---------------------------------------------------------------
1493 /** @brief returns if a group/parameter is blocking/positional */
blocking() const1494 bool blocking() const noexcept {
1495 return blocking_;
1496 }
1497
1498 /** @brief determines, if a group/parameter is blocking/positional */
blocking(bool yes)1499 Derived& blocking(bool yes) noexcept {
1500 blocking_ = yes;
1501 return *static_cast<Derived*>(this);
1502 }
1503
1504
1505 private:
1506 //---------------------------------------------------------------
1507 doc_string doc_;
1508 bool repeatable_ = false;
1509 bool blocking_ = false;
1510 };
1511
1512
1513
1514
1515 /*************************************************************************//**
1516 *
1517 * @brief sets documentation strings on a token
1518 *
1519 *****************************************************************************/
1520 template<class T>
1521 inline T&
operator %(doc_string docstr,token<T> & p)1522 operator % (doc_string docstr, token<T>& p)
1523 {
1524 return p.doc(std::move(docstr));
1525 }
1526 //---------------------------------------------------------
1527 template<class T>
1528 inline T&&
operator %(doc_string docstr,token<T> && p)1529 operator % (doc_string docstr, token<T>&& p)
1530 {
1531 return std::move(p.doc(std::move(docstr)));
1532 }
1533
1534 //---------------------------------------------------------
1535 template<class T>
1536 inline T&
operator %(token<T> & p,doc_string docstr)1537 operator % (token<T>& p, doc_string docstr)
1538 {
1539 return p.doc(std::move(docstr));
1540 }
1541 //---------------------------------------------------------
1542 template<class T>
1543 inline T&&
operator %(token<T> && p,doc_string docstr)1544 operator % (token<T>&& p, doc_string docstr)
1545 {
1546 return std::move(p.doc(std::move(docstr)));
1547 }
1548
1549
1550
1551
1552 /*************************************************************************//**
1553 *
1554 * @brief sets documentation strings on a token
1555 *
1556 *****************************************************************************/
1557 template<class T>
1558 inline T&
doc(doc_string docstr,token<T> & p)1559 doc(doc_string docstr, token<T>& p)
1560 {
1561 return p.doc(std::move(docstr));
1562 }
1563 //---------------------------------------------------------
1564 template<class T>
1565 inline T&&
doc(doc_string docstr,token<T> && p)1566 doc(doc_string docstr, token<T>&& p)
1567 {
1568 return std::move(p.doc(std::move(docstr)));
1569 }
1570
1571
1572
1573 } // namespace detail
1574
1575
1576
1577 /*************************************************************************//**
1578 *
1579 * @brief contains parameter matching functions and function classes
1580 *
1581 *****************************************************************************/
1582 namespace match {
1583
1584
1585 /*************************************************************************//**
1586 *
1587 * @brief predicate that is always true
1588 *
1589 *****************************************************************************/
1590 inline bool
any(const arg_string &)1591 any(const arg_string&) { return true; }
1592
1593 /*************************************************************************//**
1594 *
1595 * @brief predicate that is always false
1596 *
1597 *****************************************************************************/
1598 inline bool
none(const arg_string &)1599 none(const arg_string&) { return false; }
1600
1601
1602
1603 /*************************************************************************//**
1604 *
1605 * @brief predicate that returns true if the argument string is non-empty string
1606 *
1607 *****************************************************************************/
1608 inline bool
nonempty(const arg_string & s)1609 nonempty(const arg_string& s) {
1610 return !s.empty();
1611 }
1612
1613
1614
1615 /*************************************************************************//**
1616 *
1617 * @brief predicate that returns true if the argument is a non-empty
1618 * string that consists only of alphanumeric characters
1619 *
1620 *****************************************************************************/
1621 inline bool
alphanumeric(const arg_string & s)1622 alphanumeric(const arg_string& s) {
1623 if(s.empty()) return false;
1624 return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); });
1625 }
1626
1627
1628
1629 /*************************************************************************//**
1630 *
1631 * @brief predicate that returns true if the argument is a non-empty
1632 * string that consists only of alphabetic characters
1633 *
1634 *****************************************************************************/
1635 inline bool
alphabetic(const arg_string & s)1636 alphabetic(const arg_string& s) {
1637 return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); });
1638 }
1639
1640
1641
1642 /*************************************************************************//**
1643 *
1644 * @brief predicate that returns false if the argument string is
1645 * equal to any string from the exclusion list
1646 *
1647 *****************************************************************************/
1648 class none_of
1649 {
1650 public:
none_of(arg_list strs)1651 none_of(arg_list strs):
1652 excluded_{std::move(strs)}
1653 {}
1654
1655 template<class... Strings>
none_of(arg_string str,Strings &&...strs)1656 none_of(arg_string str, Strings&&... strs):
1657 excluded_{std::move(str), std::forward<Strings>(strs)...}
1658 {}
1659
1660 template<class... Strings>
none_of(const char * str,Strings &&...strs)1661 none_of(const char* str, Strings&&... strs):
1662 excluded_{arg_string(str), std::forward<Strings>(strs)...}
1663 {}
1664
operator ()(const arg_string & arg) const1665 bool operator () (const arg_string& arg) const {
1666 return (std::find(begin(excluded_), end(excluded_), arg)
1667 == end(excluded_));
1668 }
1669
1670 private:
1671 arg_list excluded_;
1672 };
1673
1674
1675
1676 /*************************************************************************//**
1677 *
1678 * @brief predicate that returns the first substring match within the input
1679 * string that rmeepresents a number
1680 * (with at maximum one decimal point and digit separators)
1681 *
1682 *****************************************************************************/
1683 class numbers
1684 {
1685 public:
1686 explicit
numbers(char decimalPoint='.',char digitSeparator=' ',char exponentSeparator='e')1687 numbers(char decimalPoint = '.',
1688 char digitSeparator = ' ',
1689 char exponentSeparator = 'e')
1690 :
1691 decpoint_{decimalPoint}, separator_{digitSeparator},
1692 exp_{exponentSeparator}
1693 {}
1694
operator ()(const arg_string & s) const1695 subrange operator () (const arg_string& s) const {
1696 return str::first_number_match(s, separator_, decpoint_, exp_);
1697 }
1698
1699 private:
1700 char decpoint_;
1701 char separator_;
1702 char exp_;
1703 };
1704
1705
1706
1707 /*************************************************************************//**
1708 *
1709 * @brief predicate that returns true if the input string represents an integer
1710 * (with optional digit separators)
1711 *
1712 *****************************************************************************/
1713 class integers {
1714 public:
1715 explicit
integers(char digitSeparator=' ')1716 integers(char digitSeparator = ' '): separator_{digitSeparator} {}
1717
operator ()(const arg_string & s) const1718 subrange operator () (const arg_string& s) const {
1719 return str::first_integer_match(s, separator_);
1720 }
1721
1722 private:
1723 char separator_;
1724 };
1725
1726
1727
1728 /*************************************************************************//**
1729 *
1730 * @brief predicate that returns true if the input string represents
1731 * a non-negative integer (with optional digit separators)
1732 *
1733 *****************************************************************************/
1734 class positive_integers {
1735 public:
1736 explicit
positive_integers(char digitSeparator=' ')1737 positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {}
1738
operator ()(const arg_string & s) const1739 subrange operator () (const arg_string& s) const {
1740 auto match = str::first_integer_match(s, separator_);
1741 if(!match) return subrange{};
1742 if(s[match.at()] == '-') return subrange{};
1743 return match;
1744 }
1745
1746 private:
1747 char separator_;
1748 };
1749
1750
1751
1752 /*************************************************************************//**
1753 *
1754 * @brief predicate that returns true if the input string
1755 * contains a given substring
1756 *
1757 *****************************************************************************/
1758 class substring
1759 {
1760 public:
1761 explicit
substring(arg_string str)1762 substring(arg_string str): str_{std::move(str)} {}
1763
operator ()(const arg_string & s) const1764 subrange operator () (const arg_string& s) const {
1765 return str::substring_match(s, str_);
1766 }
1767
1768 private:
1769 arg_string str_;
1770 };
1771
1772
1773
1774 /*************************************************************************//**
1775 *
1776 * @brief predicate that returns true if the input string starts
1777 * with a given prefix
1778 *
1779 *****************************************************************************/
1780 class prefix {
1781 public:
1782 explicit
prefix(arg_string p)1783 prefix(arg_string p): prefix_{std::move(p)} {}
1784
operator ()(const arg_string & s) const1785 bool operator () (const arg_string& s) const {
1786 return s.find(prefix_) == 0;
1787 }
1788
1789 private:
1790 arg_string prefix_;
1791 };
1792
1793
1794
1795 /*************************************************************************//**
1796 *
1797 * @brief predicate that returns true if the input string does not start
1798 * with a given prefix
1799 *
1800 *****************************************************************************/
1801 class prefix_not {
1802 public:
1803 explicit
prefix_not(arg_string p)1804 prefix_not(arg_string p): prefix_{std::move(p)} {}
1805
operator ()(const arg_string & s) const1806 bool operator () (const arg_string& s) const {
1807 return s.find(prefix_) != 0;
1808 }
1809
1810 private:
1811 arg_string prefix_;
1812 };
1813
1814
1815 /** @brief alias for prefix_not */
1816 using noprefix = prefix_not;
1817
1818
1819
1820 /*************************************************************************//**
1821 *
1822 * @brief predicate that returns true if the length of the input string
1823 * is wihtin a given interval
1824 *
1825 *****************************************************************************/
1826 class length {
1827 public:
1828 explicit
length(std::size_t exact)1829 length(std::size_t exact):
1830 min_{exact}, max_{exact}
1831 {}
1832
1833 explicit
length(std::size_t min,std::size_t max)1834 length(std::size_t min, std::size_t max):
1835 min_{min}, max_{max}
1836 {}
1837
operator ()(const arg_string & s) const1838 bool operator () (const arg_string& s) const {
1839 return s.size() >= min_ && s.size() <= max_;
1840 }
1841
1842 private:
1843 std::size_t min_;
1844 std::size_t max_;
1845 };
1846
1847
1848 /*************************************************************************//**
1849 *
1850 * @brief makes function object that returns true if the input string has a
1851 * given minimum length
1852 *
1853 *****************************************************************************/
min_length(std::size_t min)1854 inline length min_length(std::size_t min)
1855 {
1856 return length{min, arg_string::npos-1};
1857 }
1858
1859 /*************************************************************************//**
1860 *
1861 * @brief makes function object that returns true if the input string is
1862 * not longer than a given maximum length
1863 *
1864 *****************************************************************************/
max_length(std::size_t max)1865 inline length max_length(std::size_t max)
1866 {
1867 return length{0, max};
1868 }
1869
1870
1871 } // namespace match
1872
1873
1874
1875
1876
1877 /*************************************************************************//**
1878 *
1879 * @brief command line parameter that can match one or many arguments.
1880 *
1881 *****************************************************************************/
1882 class parameter :
1883 public detail::token<parameter>,
1884 public detail::action_provider<parameter>
1885 {
1886 /** @brief adapts a 'match_predicate' to the 'match_function' interface */
1887 class predicate_adapter {
1888 public:
1889 explicit
predicate_adapter(match_predicate pred)1890 predicate_adapter(match_predicate pred): match_{std::move(pred)} {}
1891
operator ()(const arg_string & arg) const1892 subrange operator () (const arg_string& arg) const {
1893 return match_(arg) ? subrange{0,arg.size()} : subrange{};
1894 }
1895
1896 private:
1897 match_predicate match_;
1898 };
1899
1900 public:
1901 //---------------------------------------------------------------
1902 /** @brief makes default parameter, that will match nothing */
parameter()1903 parameter():
1904 flags_{},
1905 matcher_{predicate_adapter{match::none}},
1906 label_{}, required_{false}, greedy_{false}
1907 {}
1908
1909 /** @brief makes "flag" parameter */
1910 template<class... Strings>
1911 explicit
parameter(arg_string str,Strings &&...strs)1912 parameter(arg_string str, Strings&&... strs):
1913 flags_{},
1914 matcher_{predicate_adapter{match::none}},
1915 label_{}, required_{false}, greedy_{false}
1916 {
1917 add_flags(std::move(str), std::forward<Strings>(strs)...);
1918 }
1919
1920 /** @brief makes "flag" parameter from range of strings */
1921 explicit
parameter(const arg_list & flaglist)1922 parameter(const arg_list& flaglist):
1923 flags_{},
1924 matcher_{predicate_adapter{match::none}},
1925 label_{}, required_{false}, greedy_{false}
1926 {
1927 add_flags(flaglist);
1928 }
1929
1930 //-----------------------------------------------------
1931 /** @brief makes "value" parameter with custom match predicate
1932 * (= yes/no matcher)
1933 */
1934 explicit
parameter(match_predicate filter)1935 parameter(match_predicate filter):
1936 flags_{},
1937 matcher_{predicate_adapter{std::move(filter)}},
1938 label_{}, required_{false}, greedy_{false}
1939 {}
1940
1941 /** @brief makes "value" parameter with custom match function
1942 * (= partial matcher)
1943 */
1944 explicit
parameter(match_function filter)1945 parameter(match_function filter):
1946 flags_{},
1947 matcher_{std::move(filter)},
1948 label_{}, required_{false}, greedy_{false}
1949 {}
1950
1951
1952 //---------------------------------------------------------------
1953 /** @brief returns if a parameter is required */
1954 bool
required() const1955 required() const noexcept {
1956 return required_;
1957 }
1958
1959 /** @brief determines if a parameter is required */
1960 parameter&
required(bool yes)1961 required(bool yes) noexcept {
1962 required_ = yes;
1963 return *this;
1964 }
1965
1966
1967 //---------------------------------------------------------------
1968 /** @brief returns if a parameter should match greedily */
1969 bool
greedy() const1970 greedy() const noexcept {
1971 return greedy_;
1972 }
1973
1974 /** @brief determines if a parameter should match greedily */
1975 parameter&
greedy(bool yes)1976 greedy(bool yes) noexcept {
1977 greedy_ = yes;
1978 return *this;
1979 }
1980
1981
1982 //---------------------------------------------------------------
1983 /** @brief returns parameter label;
1984 * will be used for documentation, if flags are empty
1985 */
1986 const doc_string&
label() const1987 label() const {
1988 return label_;
1989 }
1990
1991 /** @brief sets parameter label;
1992 * will be used for documentation, if flags are empty
1993 */
1994 parameter&
label(const doc_string & lbl)1995 label(const doc_string& lbl) {
1996 label_ = lbl;
1997 return *this;
1998 }
1999
2000 /** @brief sets parameter label;
2001 * will be used for documentation, if flags are empty
2002 */
2003 parameter&
label(doc_string && lbl)2004 label(doc_string&& lbl) {
2005 label_ = lbl;
2006 return *this;
2007 }
2008
2009
2010 //---------------------------------------------------------------
2011 /** @brief returns either longest matching prefix of 'arg' in any
2012 * of the flags or the result of the custom match operation
2013 */
2014 subrange
match(const arg_string & arg) const2015 match(const arg_string& arg) const
2016 {
2017 if(arg.empty()) return subrange{};
2018
2019 if(flags_.empty()) {
2020 return matcher_(arg);
2021 }
2022 else {
2023 if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) {
2024 return subrange{0,arg.size()};
2025 }
2026 return str::longest_prefix_match(arg, flags_);
2027 }
2028 }
2029
2030
2031 //---------------------------------------------------------------
2032 /** @brief access range of flag strings */
2033 const arg_list&
flags() const2034 flags() const noexcept {
2035 return flags_;
2036 }
2037
2038 /** @brief access custom match operation */
2039 const match_function&
matcher() const2040 matcher() const noexcept {
2041 return matcher_;
2042 }
2043
2044
2045 //---------------------------------------------------------------
2046 /** @brief prepend prefix to each flag */
2047 inline friend parameter&
with_prefix(const arg_string & prefix,parameter & p)2048 with_prefix(const arg_string& prefix, parameter& p)
2049 {
2050 if(prefix.empty() || p.flags().empty()) return p;
2051
2052 for(auto& f : p.flags_) {
2053 if(f.find(prefix) != 0) f.insert(0, prefix);
2054 }
2055 return p;
2056 }
2057
2058
2059 /** @brief prepend prefix to each flag
2060 */
2061 inline friend parameter&
with_prefixes_short_long(const arg_string & shortpfx,const arg_string & longpfx,parameter & p)2062 with_prefixes_short_long(
2063 const arg_string& shortpfx, const arg_string& longpfx,
2064 parameter& p)
2065 {
2066 if(shortpfx.empty() && longpfx.empty()) return p;
2067 if(p.flags().empty()) return p;
2068
2069 for(auto& f : p.flags_) {
2070 if(f.size() == 1) {
2071 if(f.find(shortpfx) != 0) f.insert(0, shortpfx);
2072 } else {
2073 if(f.find(longpfx) != 0) f.insert(0, longpfx);
2074 }
2075 }
2076 return p;
2077 }
2078
2079 private:
2080 //---------------------------------------------------------------
add_flags(arg_string str)2081 void add_flags(arg_string str) {
2082 //empty flags are not allowed
2083 str::remove_ws(str);
2084 if(!str.empty()) flags_.push_back(std::move(str));
2085 }
2086
2087 //---------------------------------------------------------------
add_flags(const arg_list & strs)2088 void add_flags(const arg_list& strs) {
2089 if(strs.empty()) return;
2090 flags_.reserve(flags_.size() + strs.size());
2091 for(const auto& s : strs) add_flags(s);
2092 }
2093
2094 template<class String1, class String2, class... Strings>
2095 void
add_flags(String1 && s1,String2 && s2,Strings &&...ss)2096 add_flags(String1&& s1, String2&& s2, Strings&&... ss) {
2097 flags_.reserve(2 + sizeof...(ss));
2098 add_flags(std::forward<String1>(s1));
2099 add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...);
2100 }
2101
2102 arg_list flags_;
2103 match_function matcher_;
2104 doc_string label_;
2105 bool required_ = false;
2106 bool greedy_ = false;
2107 };
2108
2109
2110
2111
2112 /*************************************************************************//**
2113 *
2114 * @brief makes required non-blocking exact match parameter
2115 *
2116 *****************************************************************************/
2117 template<class String, class... Strings>
2118 inline parameter
command(String && flag,Strings &&...flags)2119 command(String&& flag, Strings&&... flags)
2120 {
2121 return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
2122 .required(true).blocking(true).repeatable(false);
2123 }
2124
2125
2126
2127 /*************************************************************************//**
2128 *
2129 * @brief makes required non-blocking exact match parameter
2130 *
2131 *****************************************************************************/
2132 template<class String, class... Strings>
2133 inline parameter
required(String && flag,Strings &&...flags)2134 required(String&& flag, Strings&&... flags)
2135 {
2136 return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
2137 .required(true).blocking(false).repeatable(false);
2138 }
2139
2140
2141
2142 /*************************************************************************//**
2143 *
2144 * @brief makes optional, non-blocking exact match parameter
2145 *
2146 *****************************************************************************/
2147 template<class String, class... Strings>
2148 inline parameter
option(String && flag,Strings &&...flags)2149 option(String&& flag, Strings&&... flags)
2150 {
2151 return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
2152 .required(false).blocking(false).repeatable(false);
2153 }
2154
2155
2156
2157 /*************************************************************************//**
2158 *
2159 * @brief makes required, blocking, repeatable value parameter;
2160 * matches any non-empty string
2161 *
2162 *****************************************************************************/
2163 template<class... Targets>
2164 inline parameter
value(const doc_string & label,Targets &&...tgts)2165 value(const doc_string& label, Targets&&... tgts)
2166 {
2167 return parameter{match::nonempty}
2168 .label(label)
2169 .target(std::forward<Targets>(tgts)...)
2170 .required(true).blocking(true).repeatable(false);
2171 }
2172
2173 template<class Filter, class... Targets, class = typename std::enable_if<
2174 traits::is_callable<Filter,bool(const char*)>::value ||
2175 traits::is_callable<Filter,subrange(const char*)>::value>::type>
2176 inline parameter
value(Filter && filter,doc_string label,Targets &&...tgts)2177 value(Filter&& filter, doc_string label, Targets&&... tgts)
2178 {
2179 return parameter{std::forward<Filter>(filter)}
2180 .label(label)
2181 .target(std::forward<Targets>(tgts)...)
2182 .required(true).blocking(true).repeatable(false);
2183 }
2184
2185
2186
2187 /*************************************************************************//**
2188 *
2189 * @brief makes required, blocking, repeatable value parameter;
2190 * matches any non-empty string
2191 *
2192 *****************************************************************************/
2193 template<class... Targets>
2194 inline parameter
values(const doc_string & label,Targets &&...tgts)2195 values(const doc_string& label, Targets&&... tgts)
2196 {
2197 return parameter{match::nonempty}
2198 .label(label)
2199 .target(std::forward<Targets>(tgts)...)
2200 .required(true).blocking(true).repeatable(true);
2201 }
2202
2203 template<class Filter, class... Targets, class = typename std::enable_if<
2204 traits::is_callable<Filter,bool(const char*)>::value ||
2205 traits::is_callable<Filter,subrange(const char*)>::value>::type>
2206 inline parameter
values(Filter && filter,doc_string label,Targets &&...tgts)2207 values(Filter&& filter, doc_string label, Targets&&... tgts)
2208 {
2209 return parameter{std::forward<Filter>(filter)}
2210 .label(label)
2211 .target(std::forward<Targets>(tgts)...)
2212 .required(true).blocking(true).repeatable(true);
2213 }
2214
2215
2216
2217 /*************************************************************************//**
2218 *
2219 * @brief makes optional, blocking value parameter;
2220 * matches any non-empty string
2221 *
2222 *****************************************************************************/
2223 template<class... Targets>
2224 inline parameter
opt_value(const doc_string & label,Targets &&...tgts)2225 opt_value(const doc_string& label, Targets&&... tgts)
2226 {
2227 return parameter{match::nonempty}
2228 .label(label)
2229 .target(std::forward<Targets>(tgts)...)
2230 .required(false).blocking(false).repeatable(false);
2231 }
2232
2233 template<class Filter, class... Targets, class = typename std::enable_if<
2234 traits::is_callable<Filter,bool(const char*)>::value ||
2235 traits::is_callable<Filter,subrange(const char*)>::value>::type>
2236 inline parameter
opt_value(Filter && filter,doc_string label,Targets &&...tgts)2237 opt_value(Filter&& filter, doc_string label, Targets&&... tgts)
2238 {
2239 return parameter{std::forward<Filter>(filter)}
2240 .label(label)
2241 .target(std::forward<Targets>(tgts)...)
2242 .required(false).blocking(false).repeatable(false);
2243 }
2244
2245
2246
2247 /*************************************************************************//**
2248 *
2249 * @brief makes optional, blocking, repeatable value parameter;
2250 * matches any non-empty string
2251 *
2252 *****************************************************************************/
2253 template<class... Targets>
2254 inline parameter
opt_values(const doc_string & label,Targets &&...tgts)2255 opt_values(const doc_string& label, Targets&&... tgts)
2256 {
2257 return parameter{match::nonempty}
2258 .label(label)
2259 .target(std::forward<Targets>(tgts)...)
2260 .required(false).blocking(false).repeatable(true);
2261 }
2262
2263 template<class Filter, class... Targets, class = typename std::enable_if<
2264 traits::is_callable<Filter,bool(const char*)>::value ||
2265 traits::is_callable<Filter,subrange(const char*)>::value>::type>
2266 inline parameter
opt_values(Filter && filter,doc_string label,Targets &&...tgts)2267 opt_values(Filter&& filter, doc_string label, Targets&&... tgts)
2268 {
2269 return parameter{std::forward<Filter>(filter)}
2270 .label(label)
2271 .target(std::forward<Targets>(tgts)...)
2272 .required(false).blocking(false).repeatable(true);
2273 }
2274
2275
2276
2277 /*************************************************************************//**
2278 *
2279 * @brief makes required, blocking value parameter;
2280 * matches any string consisting of alphanumeric characters
2281 *
2282 *****************************************************************************/
2283 template<class... Targets>
2284 inline parameter
word(const doc_string & label,Targets &&...tgts)2285 word(const doc_string& label, Targets&&... tgts)
2286 {
2287 return parameter{match::alphanumeric}
2288 .label(label)
2289 .target(std::forward<Targets>(tgts)...)
2290 .required(true).blocking(true).repeatable(false);
2291 }
2292
2293
2294
2295 /*************************************************************************//**
2296 *
2297 * @brief makes required, blocking, repeatable value parameter;
2298 * matches any string consisting of alphanumeric characters
2299 *
2300 *****************************************************************************/
2301 template<class... Targets>
2302 inline parameter
words(const doc_string & label,Targets &&...tgts)2303 words(const doc_string& label, Targets&&... tgts)
2304 {
2305 return parameter{match::alphanumeric}
2306 .label(label)
2307 .target(std::forward<Targets>(tgts)...)
2308 .required(true).blocking(true).repeatable(true);
2309 }
2310
2311
2312
2313 /*************************************************************************//**
2314 *
2315 * @brief makes optional, blocking value parameter;
2316 * matches any string consisting of alphanumeric characters
2317 *
2318 *****************************************************************************/
2319 template<class... Targets>
2320 inline parameter
opt_word(const doc_string & label,Targets &&...tgts)2321 opt_word(const doc_string& label, Targets&&... tgts)
2322 {
2323 return parameter{match::alphanumeric}
2324 .label(label)
2325 .target(std::forward<Targets>(tgts)...)
2326 .required(false).blocking(false).repeatable(false);
2327 }
2328
2329
2330
2331 /*************************************************************************//**
2332 *
2333 * @brief makes optional, blocking, repeatable value parameter;
2334 * matches any string consisting of alphanumeric characters
2335 *
2336 *****************************************************************************/
2337 template<class... Targets>
2338 inline parameter
opt_words(const doc_string & label,Targets &&...tgts)2339 opt_words(const doc_string& label, Targets&&... tgts)
2340 {
2341 return parameter{match::alphanumeric}
2342 .label(label)
2343 .target(std::forward<Targets>(tgts)...)
2344 .required(false).blocking(false).repeatable(true);
2345 }
2346
2347
2348
2349 /*************************************************************************//**
2350 *
2351 * @brief makes required, blocking value parameter;
2352 * matches any string that represents a number
2353 *
2354 *****************************************************************************/
2355 template<class... Targets>
2356 inline parameter
number(const doc_string & label,Targets &&...tgts)2357 number(const doc_string& label, Targets&&... tgts)
2358 {
2359 return parameter{match::numbers{}}
2360 .label(label)
2361 .target(std::forward<Targets>(tgts)...)
2362 .required(true).blocking(true).repeatable(false);
2363 }
2364
2365
2366
2367 /*************************************************************************//**
2368 *
2369 * @brief makes required, blocking, repeatable value parameter;
2370 * matches any string that represents a number
2371 *
2372 *****************************************************************************/
2373 template<class... Targets>
2374 inline parameter
numbers(const doc_string & label,Targets &&...tgts)2375 numbers(const doc_string& label, Targets&&... tgts)
2376 {
2377 return parameter{match::numbers{}}
2378 .label(label)
2379 .target(std::forward<Targets>(tgts)...)
2380 .required(true).blocking(true).repeatable(true);
2381 }
2382
2383
2384
2385 /*************************************************************************//**
2386 *
2387 * @brief makes optional, blocking value parameter;
2388 * matches any string that represents a number
2389 *
2390 *****************************************************************************/
2391 template<class... Targets>
2392 inline parameter
opt_number(const doc_string & label,Targets &&...tgts)2393 opt_number(const doc_string& label, Targets&&... tgts)
2394 {
2395 return parameter{match::numbers{}}
2396 .label(label)
2397 .target(std::forward<Targets>(tgts)...)
2398 .required(false).blocking(false).repeatable(false);
2399 }
2400
2401
2402
2403 /*************************************************************************//**
2404 *
2405 * @brief makes optional, blocking, repeatable value parameter;
2406 * matches any string that represents a number
2407 *
2408 *****************************************************************************/
2409 template<class... Targets>
2410 inline parameter
opt_numbers(const doc_string & label,Targets &&...tgts)2411 opt_numbers(const doc_string& label, Targets&&... tgts)
2412 {
2413 return parameter{match::numbers{}}
2414 .label(label)
2415 .target(std::forward<Targets>(tgts)...)
2416 .required(false).blocking(false).repeatable(true);
2417 }
2418
2419
2420
2421 /*************************************************************************//**
2422 *
2423 * @brief makes required, blocking value parameter;
2424 * matches any string that represents an integer
2425 *
2426 *****************************************************************************/
2427 template<class... Targets>
2428 inline parameter
integer(const doc_string & label,Targets &&...tgts)2429 integer(const doc_string& label, Targets&&... tgts)
2430 {
2431 return parameter{match::integers{}}
2432 .label(label)
2433 .target(std::forward<Targets>(tgts)...)
2434 .required(true).blocking(true).repeatable(false);
2435 }
2436
2437
2438
2439 /*************************************************************************//**
2440 *
2441 * @brief makes required, blocking, repeatable value parameter;
2442 * matches any string that represents an integer
2443 *
2444 *****************************************************************************/
2445 template<class... Targets>
2446 inline parameter
integers(const doc_string & label,Targets &&...tgts)2447 integers(const doc_string& label, Targets&&... tgts)
2448 {
2449 return parameter{match::integers{}}
2450 .label(label)
2451 .target(std::forward<Targets>(tgts)...)
2452 .required(true).blocking(true).repeatable(true);
2453 }
2454
2455
2456
2457 /*************************************************************************//**
2458 *
2459 * @brief makes optional, blocking value parameter;
2460 * matches any string that represents an integer
2461 *
2462 *****************************************************************************/
2463 template<class... Targets>
2464 inline parameter
opt_integer(const doc_string & label,Targets &&...tgts)2465 opt_integer(const doc_string& label, Targets&&... tgts)
2466 {
2467 return parameter{match::integers{}}
2468 .label(label)
2469 .target(std::forward<Targets>(tgts)...)
2470 .required(false).blocking(false).repeatable(false);
2471 }
2472
2473
2474
2475 /*************************************************************************//**
2476 *
2477 * @brief makes optional, blocking, repeatable value parameter;
2478 * matches any string that represents an integer
2479 *
2480 *****************************************************************************/
2481 template<class... Targets>
2482 inline parameter
opt_integers(const doc_string & label,Targets &&...tgts)2483 opt_integers(const doc_string& label, Targets&&... tgts)
2484 {
2485 return parameter{match::integers{}}
2486 .label(label)
2487 .target(std::forward<Targets>(tgts)...)
2488 .required(false).blocking(false).repeatable(true);
2489 }
2490
2491
2492
2493 /*************************************************************************//**
2494 *
2495 * @brief makes catch-all value parameter
2496 *
2497 *****************************************************************************/
2498 template<class... Targets>
2499 inline parameter
any_other(Targets &&...tgts)2500 any_other(Targets&&... tgts)
2501 {
2502 return parameter{match::any}
2503 .target(std::forward<Targets>(tgts)...)
2504 .required(false).blocking(false).repeatable(true);
2505 }
2506
2507
2508
2509 /*************************************************************************//**
2510 *
2511 * @brief makes catch-all value parameter with custom filter
2512 *
2513 *****************************************************************************/
2514 template<class Filter, class... Targets, class = typename std::enable_if<
2515 traits::is_callable<Filter,bool(const char*)>::value ||
2516 traits::is_callable<Filter,subrange(const char*)>::value>::type>
2517 inline parameter
any(Filter && filter,Targets &&...tgts)2518 any(Filter&& filter, Targets&&... tgts)
2519 {
2520 return parameter{std::forward<Filter>(filter)}
2521 .target(std::forward<Targets>(tgts)...)
2522 .required(false).blocking(false).repeatable(true);
2523 }
2524
2525
2526
2527
2528 /*************************************************************************//**
2529 *
2530 * @brief group of parameters and/or other groups;
2531 * can be configured to act as a group of alternatives (exclusive match)
2532 *
2533 *****************************************************************************/
2534 class group :
2535 public detail::token<group>
2536 {
2537 //---------------------------------------------------------------
2538 /**
2539 * @brief tagged union type that either stores a parameter or a group
2540 * and provides a common interface to them
2541 * could be replaced by std::variant in the future
2542 *
2543 * Note to future self: do NOT try again to do this with
2544 * dynamic polymorphism; there are a couple of
2545 * nasty problems associated with it and the implementation
2546 * becomes bloated and needlessly complicated.
2547 */
2548 template<class Param, class Group>
2549 struct child_t {
2550 enum class type : char {param, group};
2551 public:
2552
2553 explicit
child_tclipp::group::child_t2554 child_t(const Param& v) : m_{v}, type_{type::param} {}
child_tclipp::group::child_t2555 child_t( Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {}
2556
2557 explicit
child_tclipp::group::child_t2558 child_t(const Group& g) : m_{g}, type_{type::group} {}
child_tclipp::group::child_t2559 child_t( Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {}
2560
child_tclipp::group::child_t2561 child_t(const child_t& src): type_{src.type_} {
2562 switch(type_) {
2563 default:
2564 case type::param: new(&m_)data{src.m_.param}; break;
2565 case type::group: new(&m_)data{src.m_.group}; break;
2566 }
2567 }
2568
child_tclipp::group::child_t2569 child_t(child_t&& src) noexcept : type_{src.type_} {
2570 switch(type_) {
2571 default:
2572 case type::param: new(&m_)data{std::move(src.m_.param)}; break;
2573 case type::group: new(&m_)data{std::move(src.m_.group)}; break;
2574 }
2575 }
2576
operator =clipp::group::child_t2577 child_t& operator = (const child_t& src) {
2578 destroy_content();
2579 type_ = src.type_;
2580 switch(type_) {
2581 default:
2582 case type::param: new(&m_)data{src.m_.param}; break;
2583 case type::group: new(&m_)data{src.m_.group}; break;
2584 }
2585 return *this;
2586 }
2587
operator =clipp::group::child_t2588 child_t& operator = (child_t&& src) noexcept {
2589 destroy_content();
2590 type_ = src.type_;
2591 switch(type_) {
2592 default:
2593 case type::param: new(&m_)data{std::move(src.m_.param)}; break;
2594 case type::group: new(&m_)data{std::move(src.m_.group)}; break;
2595 }
2596 return *this;
2597 }
2598
~child_tclipp::group::child_t2599 ~child_t() {
2600 destroy_content();
2601 }
2602
2603 const doc_string&
docclipp::group::child_t2604 doc() const noexcept {
2605 switch(type_) {
2606 default:
2607 case type::param: return m_.param.doc();
2608 case type::group: return m_.group.doc();
2609 }
2610 }
2611
blockingclipp::group::child_t2612 bool blocking() const noexcept {
2613 switch(type_) {
2614 case type::param: return m_.param.blocking();
2615 case type::group: return m_.group.blocking();
2616 default: return false;
2617 }
2618 }
repeatableclipp::group::child_t2619 bool repeatable() const noexcept {
2620 switch(type_) {
2621 case type::param: return m_.param.repeatable();
2622 case type::group: return m_.group.repeatable();
2623 default: return false;
2624 }
2625 }
requiredclipp::group::child_t2626 bool required() const noexcept {
2627 switch(type_) {
2628 case type::param: return m_.param.required();
2629 case type::group:
2630 return (m_.group.exclusive() && m_.group.all_required() ) ||
2631 (!m_.group.exclusive() && m_.group.any_required() );
2632 default: return false;
2633 }
2634 }
exclusiveclipp::group::child_t2635 bool exclusive() const noexcept {
2636 switch(type_) {
2637 case type::group: return m_.group.exclusive();
2638 case type::param:
2639 default: return false;
2640 }
2641 }
param_countclipp::group::child_t2642 std::size_t param_count() const noexcept {
2643 switch(type_) {
2644 case type::group: return m_.group.param_count();
2645 case type::param:
2646 default: return std::size_t(1);
2647 }
2648 }
depthclipp::group::child_t2649 std::size_t depth() const noexcept {
2650 switch(type_) {
2651 case type::group: return m_.group.depth();
2652 case type::param:
2653 default: return std::size_t(0);
2654 }
2655 }
2656
execute_actionsclipp::group::child_t2657 void execute_actions(const arg_string& arg) const {
2658 switch(type_) {
2659 default:
2660 case type::group: return;
2661 case type::param: m_.param.execute_actions(arg); break;
2662 }
2663
2664 }
2665
notify_repeatedclipp::group::child_t2666 void notify_repeated(arg_index idx) const {
2667 switch(type_) {
2668 default:
2669 case type::group: return;
2670 case type::param: m_.param.notify_repeated(idx); break;
2671 }
2672 }
notify_missingclipp::group::child_t2673 void notify_missing(arg_index idx) const {
2674 switch(type_) {
2675 default:
2676 case type::group: return;
2677 case type::param: m_.param.notify_missing(idx); break;
2678 }
2679 }
notify_blockedclipp::group::child_t2680 void notify_blocked(arg_index idx) const {
2681 switch(type_) {
2682 default:
2683 case type::group: return;
2684 case type::param: m_.param.notify_blocked(idx); break;
2685 }
2686 }
notify_conflictclipp::group::child_t2687 void notify_conflict(arg_index idx) const {
2688 switch(type_) {
2689 default:
2690 case type::group: return;
2691 case type::param: m_.param.notify_conflict(idx); break;
2692 }
2693 }
2694
is_paramclipp::group::child_t2695 bool is_param() const noexcept { return type_ == type::param; }
is_groupclipp::group::child_t2696 bool is_group() const noexcept { return type_ == type::group; }
2697
as_paramclipp::group::child_t2698 Param& as_param() noexcept { return m_.param; }
as_groupclipp::group::child_t2699 Group& as_group() noexcept { return m_.group; }
2700
as_paramclipp::group::child_t2701 const Param& as_param() const noexcept { return m_.param; }
as_groupclipp::group::child_t2702 const Group& as_group() const noexcept { return m_.group; }
2703
2704 private:
destroy_contentclipp::group::child_t2705 void destroy_content() {
2706 switch(type_) {
2707 default:
2708 case type::param: m_.param.~Param(); break;
2709 case type::group: m_.group.~Group(); break;
2710 }
2711 }
2712
2713 union data {
data()2714 data() {}
2715
data(const Param & v)2716 data(const Param& v) : param{v} {}
data(Param && v)2717 data( Param&& v) noexcept : param{std::move(v)} {}
2718
data(const Group & g)2719 data(const Group& g) : group{g} {}
data(Group && g)2720 data( Group&& g) noexcept : group{std::move(g)} {}
~data()2721 ~data() {}
2722
2723 Param param;
2724 Group group;
2725 };
2726
2727 data m_;
2728 type type_;
2729 };
2730
2731
2732 public:
2733 //---------------------------------------------------------------
2734 using child = child_t<parameter,group>;
2735 using value_type = child;
2736
2737 private:
2738 using children_store = std::vector<child>;
2739
2740 public:
2741 using const_iterator = children_store::const_iterator;
2742 using iterator = children_store::iterator;
2743 using size_type = children_store::size_type;
2744
2745
2746 //---------------------------------------------------------------
2747 /**
2748 * @brief recursively iterates over all nodes
2749 */
2750 class depth_first_traverser
2751 {
2752 public:
2753 //-----------------------------------------------------
2754 struct context {
2755 context() = default;
contextclipp::group::depth_first_traverser::context2756 context(const group& p):
2757 parent{&p}, cur{p.begin()}, end{p.end()}
2758 {}
2759 const group* parent = nullptr;
2760 const_iterator cur;
2761 const_iterator end;
2762 };
2763 using context_list = std::vector<context>;
2764
2765 //-----------------------------------------------------
2766 class memento {
2767 friend class depth_first_traverser;
2768 int level_;
2769 context context_;
2770 public:
level() const2771 int level() const noexcept { return level_; }
param() const2772 const child* param() const noexcept { return &(*context_.cur); }
2773 };
2774
2775 depth_first_traverser() = default;
2776
2777 explicit
depth_first_traverser(const group & cur)2778 depth_first_traverser(const group& cur): stack_{} {
2779 if(!cur.empty()) stack_.emplace_back(cur);
2780 }
2781
operator bool() const2782 explicit operator bool() const noexcept {
2783 return !stack_.empty();
2784 }
2785
level() const2786 int level() const noexcept {
2787 return int(stack_.size());
2788 }
2789
is_first_in_group() const2790 bool is_first_in_group() const noexcept {
2791 if(stack_.empty()) return false;
2792 return (stack_.back().cur == stack_.back().parent->begin());
2793 }
2794
is_last_in_group() const2795 bool is_last_in_group() const noexcept {
2796 if(stack_.empty()) return false;
2797 return (stack_.back().cur+1 == stack_.back().end);
2798 }
2799
is_last_in_path() const2800 bool is_last_in_path() const noexcept {
2801 if(stack_.empty()) return false;
2802 for(const auto& t : stack_) {
2803 if(t.cur+1 != t.end) return false;
2804 }
2805 const auto& top = stack_.back();
2806 //if we have to descend into group on next ++ => not last in path
2807 if(top.cur->is_group()) return false;
2808 return true;
2809 }
2810
2811 /** @brief inside a group of alternatives >= minlevel */
is_alternative(int minlevel=0) const2812 bool is_alternative(int minlevel = 0) const noexcept {
2813 if(stack_.empty()) return false;
2814 if(minlevel > 0) minlevel -= 1;
2815 if(minlevel >= int(stack_.size())) return false;
2816 return std::any_of(stack_.begin() + minlevel, stack_.end(),
2817 [](const context& c) { return c.parent->exclusive(); });
2818 }
2819
2820 /** @brief repeatable or inside a repeatable group >= minlevel */
is_repeatable(int minlevel=0) const2821 bool is_repeatable(int minlevel = 0) const noexcept {
2822 if(stack_.empty()) return false;
2823 if(stack_.back().cur->repeatable()) return true;
2824 if(minlevel > 0) minlevel -= 1;
2825 if(minlevel >= int(stack_.size())) return false;
2826 return std::any_of(stack_.begin() + minlevel, stack_.end(),
2827 [](const context& c) { return c.parent->repeatable(); });
2828 }
2829 /** @brief inside group with joinable flags */
joinable() const2830 bool joinable() const noexcept {
2831 if(stack_.empty()) return false;
2832 return std::any_of(stack_.begin(), stack_.end(),
2833 [](const context& c) { return c.parent->joinable(); });
2834 }
2835
2836 const context_list&
stack() const2837 stack() const {
2838 return stack_;
2839 }
2840
2841 /** @brief innermost repeat group */
2842 const group*
repeat_group() const2843 repeat_group() const noexcept {
2844 auto i = std::find_if(stack_.rbegin(), stack_.rend(),
2845 [](const context& c) { return c.parent->repeatable(); });
2846
2847 return i != stack_.rend() ? i->parent : nullptr;
2848 }
2849
2850 /** @brief outermost join group */
2851 const group*
join_group() const2852 join_group() const noexcept {
2853 auto i = std::find_if(stack_.begin(), stack_.end(),
2854 [](const context& c) { return c.parent->joinable(); });
2855 return i != stack_.end() ? i->parent : nullptr;
2856 }
2857
root() const2858 const group* root() const noexcept {
2859 return stack_.empty() ? nullptr : stack_.front().parent;
2860 }
2861
2862 /** @brief common flag prefix of all flags in current group */
common_flag_prefix() const2863 arg_string common_flag_prefix() const noexcept {
2864 if(stack_.empty()) return "";
2865 auto g = join_group();
2866 return g ? g->common_flag_prefix() : arg_string("");
2867 }
2868
2869 const child&
operator *() const2870 operator * () const noexcept {
2871 return *stack_.back().cur;
2872 }
2873
2874 const child*
operator ->() const2875 operator -> () const noexcept {
2876 return &(*stack_.back().cur);
2877 }
2878
2879 const group&
parent() const2880 parent() const noexcept {
2881 return *(stack_.back().parent);
2882 }
2883
2884
2885 /** @brief go to next element of depth first search */
2886 depth_first_traverser&
operator ++()2887 operator ++ () {
2888 if(stack_.empty()) return *this;
2889 //at group -> decend into group
2890 if(stack_.back().cur->is_group()) {
2891 stack_.emplace_back(stack_.back().cur->as_group());
2892 }
2893 else {
2894 next_sibling();
2895 }
2896 return *this;
2897 }
2898
2899 /** @brief go to next sibling of current */
2900 depth_first_traverser&
next_sibling()2901 next_sibling() {
2902 if(stack_.empty()) return *this;
2903 ++stack_.back().cur;
2904 //at the end of current group?
2905 while(stack_.back().cur == stack_.back().end) {
2906 //go to parent
2907 stack_.pop_back();
2908 if(stack_.empty()) return *this;
2909 //go to next sibling in parent
2910 ++stack_.back().cur;
2911 }
2912 return *this;
2913 }
2914
2915 /** @brief go to next position after siblings of current */
2916 depth_first_traverser&
next_after_siblings()2917 next_after_siblings() {
2918 if(stack_.empty()) return *this;
2919 stack_.back().cur = stack_.back().end-1;
2920 next_sibling();
2921 return *this;
2922 }
2923
2924 /** @brief skips to next alternative in innermost group
2925 */
2926 depth_first_traverser&
next_alternative()2927 next_alternative() {
2928 if(stack_.empty()) return *this;
2929
2930 //find first exclusive group (from the top of the stack!)
2931 auto i = std::find_if(stack_.rbegin(), stack_.rend(),
2932 [](const context& c) { return c.parent->exclusive(); });
2933 if(i == stack_.rend()) return *this;
2934
2935 stack_.erase(i.base(), stack_.end());
2936 next_sibling();
2937 return *this;
2938 }
2939
2940 /**
2941 * @brief
2942 */
2943 depth_first_traverser&
back_to_parent()2944 back_to_parent() {
2945 if(stack_.empty()) return *this;
2946 stack_.pop_back();
2947 return *this;
2948 }
2949
2950 /** @brief don't visit next siblings, go back to parent on next ++
2951 * note: renders siblings unreachable for *this
2952 **/
2953 depth_first_traverser&
skip_siblings()2954 skip_siblings() {
2955 if(stack_.empty()) return *this;
2956 //future increments won't visit subsequent siblings:
2957 stack_.back().end = stack_.back().cur+1;
2958 return *this;
2959 }
2960
2961 /** @brief skips all other alternatives in surrounding exclusive groups
2962 * on next ++
2963 * note: renders alternatives unreachable for *this
2964 */
2965 depth_first_traverser&
skip_alternatives()2966 skip_alternatives() {
2967 if(stack_.empty()) return *this;
2968
2969 //exclude all other alternatives in surrounding groups
2970 //by making their current position the last one
2971 for(auto& c : stack_) {
2972 if(c.parent && c.parent->exclusive() && c.cur < c.end)
2973 c.end = c.cur+1;
2974 }
2975
2976 return *this;
2977 }
2978
invalidate()2979 void invalidate() {
2980 stack_.clear();
2981 }
2982
operator ==(const depth_first_traverser & a,const depth_first_traverser & b)2983 inline friend bool operator == (const depth_first_traverser& a,
2984 const depth_first_traverser& b)
2985 {
2986 if(a.stack_.empty() || b.stack_.empty()) return false;
2987
2988 //parents not the same -> different position
2989 if(a.stack_.back().parent != b.stack_.back().parent) return false;
2990
2991 bool aEnd = a.stack_.back().cur == a.stack_.back().end;
2992 bool bEnd = b.stack_.back().cur == b.stack_.back().end;
2993 //either both at the end of the same parent => same position
2994 if(aEnd && bEnd) return true;
2995 //or only one at the end => not at the same position
2996 if(aEnd || bEnd) return false;
2997 return std::addressof(*a.stack_.back().cur) ==
2998 std::addressof(*b.stack_.back().cur);
2999 }
operator !=(const depth_first_traverser & a,const depth_first_traverser & b)3000 inline friend bool operator != (const depth_first_traverser& a,
3001 const depth_first_traverser& b)
3002 {
3003 return !(a == b);
3004 }
3005
3006 memento
undo_point() const3007 undo_point() const {
3008 memento m;
3009 m.level_ = int(stack_.size());
3010 if(!stack_.empty()) m.context_ = stack_.back();
3011 return m;
3012 }
3013
undo(const memento & m)3014 void undo(const memento& m) {
3015 if(m.level_ < 1) return;
3016 if(m.level_ <= int(stack_.size())) {
3017 stack_.erase(stack_.begin() + m.level_, stack_.end());
3018 stack_.back() = m.context_;
3019 }
3020 else if(stack_.empty() && m.level_ == 1) {
3021 stack_.push_back(m.context_);
3022 }
3023 }
3024
3025 private:
3026 context_list stack_;
3027 };
3028
3029
3030 //---------------------------------------------------------------
3031 group() = default;
3032
3033 template<class Param, class... Params>
3034 explicit
group(doc_string docstr,Param param,Params...params)3035 group(doc_string docstr, Param param, Params... params):
3036 children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
3037 {
3038 doc(std::move(docstr));
3039 push_back(std::move(param), std::move(params)...);
3040 }
3041
3042 template<class... Params>
3043 explicit
group(parameter param,Params...params)3044 group(parameter param, Params... params):
3045 children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
3046 {
3047 push_back(std::move(param), std::move(params)...);
3048 }
3049
3050 template<class P2, class... Ps>
3051 explicit
group(group p1,P2 p2,Ps...ps)3052 group(group p1, P2 p2, Ps... ps):
3053 children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
3054 {
3055 push_back(std::move(p1), std::move(p2), std::move(ps)...);
3056 }
3057
3058
3059 //-----------------------------------------------------
3060 group(const group&) = default;
3061 group(group&&) = default;
3062
3063
3064 //---------------------------------------------------------------
3065 group& operator = (const group&) = default;
3066 group& operator = (group&&) = default;
3067
3068
3069 //---------------------------------------------------------------
3070 /** @brief determines if a command line argument can be matched by a
3071 * combination of (partial) matches through any number of children
3072 */
joinable(bool yes)3073 group& joinable(bool yes) {
3074 joinable_ = yes;
3075 return *this;
3076 }
3077
3078 /** @brief returns if a command line argument can be matched by a
3079 * combination of (partial) matches through any number of children
3080 */
joinable() const3081 bool joinable() const noexcept {
3082 return joinable_;
3083 }
3084
3085
3086 //---------------------------------------------------------------
3087 /** @brief turns explicit scoping on or off
3088 * operators , & | and other combinating functions will
3089 * not merge groups that are marked as scoped
3090 */
scoped(bool yes)3091 group& scoped(bool yes) {
3092 scoped_ = yes;
3093 return *this;
3094 }
3095
3096 /** @brief returns true if operators , & | and other combinating functions
3097 * will merge groups and false otherwise
3098 */
scoped() const3099 bool scoped() const noexcept
3100 {
3101 return scoped_;
3102 }
3103
3104
3105 //---------------------------------------------------------------
3106 /** @brief determines if children are mutually exclusive alternatives */
exclusive(bool yes)3107 group& exclusive(bool yes) {
3108 exclusive_ = yes;
3109 return *this;
3110 }
3111 /** @brief returns if children are mutually exclusive alternatives */
exclusive() const3112 bool exclusive() const noexcept {
3113 return exclusive_;
3114 }
3115
3116
3117 //---------------------------------------------------------------
3118 /** @brief returns true, if any child is required to match */
any_required() const3119 bool any_required() const
3120 {
3121 return std::any_of(children_.begin(), children_.end(),
3122 [](const child& n){ return n.required(); });
3123 }
3124 /** @brief returns true, if all children are required to match */
all_required() const3125 bool all_required() const
3126 {
3127 return std::all_of(children_.begin(), children_.end(),
3128 [](const child& n){ return n.required(); });
3129 }
3130
3131
3132 //---------------------------------------------------------------
3133 /** @brief returns true if any child is optional (=non-required) */
any_optional() const3134 bool any_optional() const {
3135 return !all_required();
3136 }
3137 /** @brief returns true if all children are optional (=non-required) */
all_optional() const3138 bool all_optional() const {
3139 return !any_required();
3140 }
3141
3142
3143 //---------------------------------------------------------------
3144 /** @brief returns if the entire group is blocking / positional */
blocking() const3145 bool blocking() const noexcept {
3146 return token<group>::blocking() || (exclusive() && all_blocking());
3147 }
3148 //-----------------------------------------------------
3149 /** @brief determines if the entire group is blocking / positional */
blocking(bool yes)3150 group& blocking(bool yes) {
3151 return token<group>::blocking(yes);
3152 }
3153
3154 //---------------------------------------------------------------
3155 /** @brief returns true if any child is blocking */
any_blocking() const3156 bool any_blocking() const
3157 {
3158 return std::any_of(children_.begin(), children_.end(),
3159 [](const child& n){ return n.blocking(); });
3160 }
3161 //---------------------------------------------------------------
3162 /** @brief returns true if all children is blocking */
all_blocking() const3163 bool all_blocking() const
3164 {
3165 return std::all_of(children_.begin(), children_.end(),
3166 [](const child& n){ return n.blocking(); });
3167 }
3168
3169
3170 //---------------------------------------------------------------
3171 /** @brief returns if any child is a value parameter (recursive) */
any_flagless() const3172 bool any_flagless() const
3173 {
3174 return std::any_of(children_.begin(), children_.end(),
3175 [](const child& p){
3176 return p.is_param() && p.as_param().flags().empty();
3177 });
3178 }
3179 /** @brief returns if all children are value parameters (recursive) */
all_flagless() const3180 bool all_flagless() const
3181 {
3182 return std::all_of(children_.begin(), children_.end(),
3183 [](const child& p){
3184 return p.is_param() && p.as_param().flags().empty();
3185 });
3186 }
3187
3188
3189 //---------------------------------------------------------------
3190 /** @brief adds child parameter at the end */
3191 group&
push_back(const parameter & v)3192 push_back(const parameter& v) {
3193 children_.emplace_back(v);
3194 return *this;
3195 }
3196 //-----------------------------------------------------
3197 /** @brief adds child parameter at the end */
3198 group&
push_back(parameter && v)3199 push_back(parameter&& v) {
3200 children_.emplace_back(std::move(v));
3201 return *this;
3202 }
3203 //-----------------------------------------------------
3204 /** @brief adds child group at the end */
3205 group&
push_back(const group & g)3206 push_back(const group& g) {
3207 children_.emplace_back(g);
3208 return *this;
3209 }
3210 //-----------------------------------------------------
3211 /** @brief adds child group at the end */
3212 group&
push_back(group && g)3213 push_back(group&& g) {
3214 children_.emplace_back(std::move(g));
3215 return *this;
3216 }
3217
3218
3219 //-----------------------------------------------------
3220 /** @brief adds children (groups and/or parameters) */
3221 template<class Param1, class Param2, class... Params>
3222 group&
push_back(Param1 && param1,Param2 && param2,Params &&...params)3223 push_back(Param1&& param1, Param2&& param2, Params&&... params)
3224 {
3225 children_.reserve(children_.size() + 2 + sizeof...(params));
3226 push_back(std::forward<Param1>(param1));
3227 push_back(std::forward<Param2>(param2), std::forward<Params>(params)...);
3228 return *this;
3229 }
3230
3231
3232 //---------------------------------------------------------------
3233 /** @brief adds child parameter at the beginning */
3234 group&
push_front(const parameter & v)3235 push_front(const parameter& v) {
3236 children_.emplace(children_.begin(), v);
3237 return *this;
3238 }
3239 //-----------------------------------------------------
3240 /** @brief adds child parameter at the beginning */
3241 group&
push_front(parameter && v)3242 push_front(parameter&& v) {
3243 children_.emplace(children_.begin(), std::move(v));
3244 return *this;
3245 }
3246 //-----------------------------------------------------
3247 /** @brief adds child group at the beginning */
3248 group&
push_front(const group & g)3249 push_front(const group& g) {
3250 children_.emplace(children_.begin(), g);
3251 return *this;
3252 }
3253 //-----------------------------------------------------
3254 /** @brief adds child group at the beginning */
3255 group&
push_front(group && g)3256 push_front(group&& g) {
3257 children_.emplace(children_.begin(), std::move(g));
3258 return *this;
3259 }
3260
3261
3262 //---------------------------------------------------------------
3263 /** @brief adds all children of other group at the end */
3264 group&
merge(group && g)3265 merge(group&& g)
3266 {
3267 children_.insert(children_.end(),
3268 std::make_move_iterator(g.begin()),
3269 std::make_move_iterator(g.end()));
3270 return *this;
3271 }
3272 //-----------------------------------------------------
3273 /** @brief adds all children of several other groups at the end */
3274 template<class... Groups>
3275 group&
merge(group && g1,group && g2,Groups &&...gs)3276 merge(group&& g1, group&& g2, Groups&&... gs)
3277 {
3278 merge(std::move(g1));
3279 merge(std::move(g2), std::forward<Groups>(gs)...);
3280 return *this;
3281 }
3282
3283
3284 //---------------------------------------------------------------
3285 /** @brief indexed, nutable access to child */
operator [](size_type index)3286 child& operator [] (size_type index) noexcept {
3287 return children_[index];
3288 }
3289 /** @brief indexed, non-nutable access to child */
operator [](size_type index) const3290 const child& operator [] (size_type index) const noexcept {
3291 return children_[index];
3292 }
3293
3294 //---------------------------------------------------------------
3295 /** @brief mutable access to first child */
front()3296 child& front() noexcept { return children_.front(); }
3297 /** @brief non-mutable access to first child */
front() const3298 const child& front() const noexcept { return children_.front(); }
3299 //-----------------------------------------------------
3300 /** @brief mutable access to last child */
back()3301 child& back() noexcept { return children_.back(); }
3302 /** @brief non-mutable access to last child */
back() const3303 const child& back() const noexcept { return children_.back(); }
3304
3305
3306 //---------------------------------------------------------------
3307 /** @brief returns true, if group has no children, false otherwise */
empty() const3308 bool empty() const noexcept { return children_.empty(); }
3309
3310 /** @brief returns number of children */
size() const3311 size_type size() const noexcept { return children_.size(); }
3312
3313 /** @brief returns number of nested levels; 1 for a flat group */
depth() const3314 size_type depth() const {
3315 size_type n = 0;
3316 for(const auto& c : children_) {
3317 auto l = 1 + c.depth();
3318 if(l > n) n = l;
3319 }
3320 return n;
3321 }
3322
3323
3324 //---------------------------------------------------------------
3325 /** @brief returns mutating iterator to position of first element */
begin()3326 iterator begin() noexcept { return children_.begin(); }
3327 /** @brief returns non-mutating iterator to position of first element */
begin() const3328 const_iterator begin() const noexcept { return children_.begin(); }
3329 /** @brief returns non-mutating iterator to position of first element */
cbegin() const3330 const_iterator cbegin() const noexcept { return children_.begin(); }
3331
3332 /** @brief returns mutating iterator to position one past the last element */
end()3333 iterator end() noexcept { return children_.end(); }
3334 /** @brief returns non-mutating iterator to position one past the last element */
end() const3335 const_iterator end() const noexcept { return children_.end(); }
3336 /** @brief returns non-mutating iterator to position one past the last element */
cend() const3337 const_iterator cend() const noexcept { return children_.end(); }
3338
3339
3340 //---------------------------------------------------------------
3341 /** @brief returns augmented iterator for depth first searches
3342 * @details taverser knows end of iteration and can skip over children
3343 */
3344 depth_first_traverser
begin_dfs() const3345 begin_dfs() const noexcept {
3346 return depth_first_traverser{*this};
3347 }
3348
3349
3350 //---------------------------------------------------------------
3351 /** @brief returns recursive parameter count */
param_count() const3352 size_type param_count() const {
3353 size_type c = 0;
3354 for(const auto& n : children_) {
3355 c += n.param_count();
3356 }
3357 return c;
3358 }
3359
3360
3361 //---------------------------------------------------------------
3362 /** @brief returns range of all flags (recursive) */
all_flags() const3363 arg_list all_flags() const
3364 {
3365 std::vector<arg_string> all;
3366 gather_flags(children_, all);
3367 return all;
3368 }
3369
3370 /** @brief returns true, if no flag occurs as true
3371 * prefix of any other flag (identical flags will be ignored) */
flags_are_prefix_free() const3372 bool flags_are_prefix_free() const
3373 {
3374 const auto fs = all_flags();
3375
3376 using std::begin; using std::end;
3377 for(auto i = begin(fs), e = end(fs); i != e; ++i) {
3378 if(!i->empty()) {
3379 for(auto j = i+1; j != e; ++j) {
3380 if(!j->empty() && *i != *j) {
3381 if(i->find(*j) == 0) return false;
3382 if(j->find(*i) == 0) return false;
3383 }
3384 }
3385 }
3386 }
3387
3388 return true;
3389 }
3390
3391
3392 //---------------------------------------------------------------
3393 /** @brief returns longest common prefix of all flags */
common_flag_prefix() const3394 arg_string common_flag_prefix() const
3395 {
3396 arg_list prefixes;
3397 gather_prefixes(children_, prefixes);
3398 return str::longest_common_prefix(prefixes);
3399 }
3400
3401
3402 private:
3403 //---------------------------------------------------------------
3404 static void
gather_flags(const children_store & nodes,arg_list & all)3405 gather_flags(const children_store& nodes, arg_list& all)
3406 {
3407 for(const auto& p : nodes) {
3408 if(p.is_group()) {
3409 gather_flags(p.as_group().children_, all);
3410 }
3411 else {
3412 const auto& pf = p.as_param().flags();
3413 using std::begin;
3414 using std::end;
3415 if(!pf.empty()) all.insert(end(all), begin(pf), end(pf));
3416 }
3417 }
3418 }
3419 //---------------------------------------------------------------
3420 static void
gather_prefixes(const children_store & nodes,arg_list & all)3421 gather_prefixes(const children_store& nodes, arg_list& all)
3422 {
3423 for(const auto& p : nodes) {
3424 if(p.is_group()) {
3425 gather_prefixes(p.as_group().children_, all);
3426 }
3427 else if(!p.as_param().flags().empty()) {
3428 auto pfx = str::longest_common_prefix(p.as_param().flags());
3429 if(!pfx.empty()) all.push_back(std::move(pfx));
3430 }
3431 }
3432 }
3433
3434 //---------------------------------------------------------------
3435 children_store children_;
3436 bool exclusive_ = false;
3437 bool joinable_ = false;
3438 bool scoped_ = false;
3439 };
3440
3441
3442
3443 /*************************************************************************//**
3444 *
3445 * @brief group or parameter
3446 *
3447 *****************************************************************************/
3448 using pattern = group::child;
3449
3450
3451
3452 /*************************************************************************//**
3453 *
3454 * @brief apply an action to all parameters in a group
3455 *
3456 *****************************************************************************/
3457 template<class Action>
for_all_params(group & g,Action && action)3458 void for_all_params(group& g, Action&& action)
3459 {
3460 for(auto& p : g) {
3461 if(p.is_group()) {
3462 for_all_params(p.as_group(), action);
3463 }
3464 else {
3465 action(p.as_param());
3466 }
3467 }
3468 }
3469
3470 template<class Action>
for_all_params(const group & g,Action && action)3471 void for_all_params(const group& g, Action&& action)
3472 {
3473 for(auto& p : g) {
3474 if(p.is_group()) {
3475 for_all_params(p.as_group(), action);
3476 }
3477 else {
3478 action(p.as_param());
3479 }
3480 }
3481 }
3482
3483
3484
3485 /*************************************************************************//**
3486 *
3487 * @brief makes a group of parameters and/or groups
3488 *
3489 *****************************************************************************/
3490 inline group
operator ,(parameter a,parameter b)3491 operator , (parameter a, parameter b)
3492 {
3493 return group{std::move(a), std::move(b)}.scoped(false);
3494 }
3495
3496 //---------------------------------------------------------
3497 inline group
operator ,(parameter a,group b)3498 operator , (parameter a, group b)
3499 {
3500 return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable()
3501 && !b.joinable() && (b.doc().empty() || b.doc() == a.doc())
3502 ? b.push_front(std::move(a))
3503 : group{std::move(a), std::move(b)}.scoped(false);
3504 }
3505
3506 //---------------------------------------------------------
3507 inline group
operator ,(group a,parameter b)3508 operator , (group a, parameter b)
3509 {
3510 return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
3511 && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
3512 ? a.push_back(std::move(b))
3513 : group{std::move(a), std::move(b)}.scoped(false);
3514 }
3515
3516 //---------------------------------------------------------
3517 inline group
operator ,(group a,group b)3518 operator , (group a, group b)
3519 {
3520 return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
3521 && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
3522 ? a.push_back(std::move(b))
3523 : group{std::move(a), std::move(b)}.scoped(false);
3524 }
3525
3526
3527
3528 /*************************************************************************//**
3529 *
3530 * @brief makes a group of alternative parameters or groups
3531 *
3532 *****************************************************************************/
3533 template<class Param, class... Params>
3534 inline group
one_of(Param param,Params...params)3535 one_of(Param param, Params... params)
3536 {
3537 return group{std::move(param), std::move(params)...}.exclusive(true);
3538 }
3539
3540
3541 /*************************************************************************//**
3542 *
3543 * @brief makes a group of alternative parameters or groups
3544 *
3545 *****************************************************************************/
3546 inline group
operator |(parameter a,parameter b)3547 operator | (parameter a, parameter b)
3548 {
3549 return group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
3550 }
3551
3552 //-------------------------------------------------------------------
3553 inline group
operator |(parameter a,group b)3554 operator | (parameter a, group b)
3555 {
3556 return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable()
3557 && !b.joinable()
3558 && (b.doc().empty() || b.doc() == a.doc())
3559 ? b.push_front(std::move(a))
3560 : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
3561 }
3562
3563 //-------------------------------------------------------------------
3564 inline group
operator |(group a,parameter b)3565 operator | (group a, parameter b)
3566 {
3567 return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable()
3568 && a.blocking() == b.blocking()
3569 && (a.doc().empty() || a.doc() == b.doc())
3570 ? a.push_back(std::move(b))
3571 : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
3572 }
3573
3574 inline group
operator |(group a,group b)3575 operator | (group a, group b)
3576 {
3577 return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable()
3578 && a.blocking() == b.blocking()
3579 && (a.doc().empty() || a.doc() == b.doc())
3580 ? a.push_back(std::move(b))
3581 : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
3582 }
3583
3584
3585
3586 /*************************************************************************//**
3587 *
3588 * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
3589 * no interface guarantees; might be changed or removed in the future
3590 *
3591 *****************************************************************************/
3592 namespace detail {
3593
set_blocking(bool)3594 inline void set_blocking(bool) {}
3595
3596 template<class P, class... Ps>
set_blocking(bool yes,P & p,Ps &...ps)3597 void set_blocking(bool yes, P& p, Ps&... ps) {
3598 p.blocking(yes);
3599 set_blocking(yes, ps...);
3600 }
3601
3602 } // namespace detail
3603
3604
3605 /*************************************************************************//**
3606 *
3607 * @brief makes a parameter/group sequence by making all input objects blocking
3608 *
3609 *****************************************************************************/
3610 template<class Param, class... Params>
3611 inline group
in_sequence(Param param,Params...params)3612 in_sequence(Param param, Params... params)
3613 {
3614 detail::set_blocking(true, param, params...);
3615 return group{std::move(param), std::move(params)...}.scoped(true);
3616 }
3617
3618
3619 /*************************************************************************//**
3620 *
3621 * @brief makes a parameter/group sequence by making all input objects blocking
3622 *
3623 *****************************************************************************/
3624 inline group
operator &(parameter a,parameter b)3625 operator & (parameter a, parameter b)
3626 {
3627 a.blocking(true);
3628 b.blocking(true);
3629 return group{std::move(a), std::move(b)}.scoped(true);
3630 }
3631
3632 //---------------------------------------------------------
3633 inline group
operator &(parameter a,group b)3634 operator & (parameter a, group b)
3635 {
3636 a.blocking(true);
3637 return group{std::move(a), std::move(b)}.scoped(true);
3638 }
3639
3640 //---------------------------------------------------------
3641 inline group
operator &(group a,parameter b)3642 operator & (group a, parameter b)
3643 {
3644 b.blocking(true);
3645 if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable()
3646 && (a.doc().empty() || a.doc() == b.doc()))
3647 {
3648 return a.push_back(std::move(b));
3649 }
3650 else {
3651 if(!a.all_blocking()) a.blocking(true);
3652 return group{std::move(a), std::move(b)}.scoped(true);
3653 }
3654 }
3655
3656 inline group
operator &(group a,group b)3657 operator & (group a, group b)
3658 {
3659 if(!b.all_blocking()) b.blocking(true);
3660 if(a.all_blocking() && !a.exclusive() && !a.repeatable()
3661 && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()))
3662 {
3663 return a.push_back(std::move(b));
3664 }
3665 else {
3666 if(!a.all_blocking()) a.blocking(true);
3667 return group{std::move(a), std::move(b)}.scoped(true);
3668 }
3669 }
3670
3671
3672
3673 /*************************************************************************//**
3674 *
3675 * @brief makes a group of parameters and/or groups
3676 * where all single char flag params ("-a", "b", ...) are joinable
3677 *
3678 *****************************************************************************/
3679 inline group
joinable(group g)3680 joinable(group g) {
3681 return g.joinable(true);
3682 }
3683
3684 //-------------------------------------------------------------------
3685 template<class... Params>
3686 inline group
joinable(parameter param,Params...params)3687 joinable(parameter param, Params... params)
3688 {
3689 return group{std::move(param), std::move(params)...}.joinable(true);
3690 }
3691
3692 template<class P2, class... Ps>
3693 inline group
joinable(group p1,P2 p2,Ps...ps)3694 joinable(group p1, P2 p2, Ps... ps)
3695 {
3696 return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true);
3697 }
3698
3699 template<class Param, class... Params>
3700 inline group
joinable(doc_string docstr,Param param,Params...params)3701 joinable(doc_string docstr, Param param, Params... params)
3702 {
3703 return group{std::move(param), std::move(params)...}
3704 .joinable(true).doc(std::move(docstr));
3705 }
3706
3707
3708
3709 /*************************************************************************//**
3710 *
3711 * @brief makes a repeatable copy of a parameter
3712 *
3713 *****************************************************************************/
3714 inline parameter
repeatable(parameter p)3715 repeatable(parameter p) {
3716 return p.repeatable(true);
3717 }
3718
3719 /*************************************************************************//**
3720 *
3721 * @brief makes a repeatable copy of a group
3722 *
3723 *****************************************************************************/
3724 inline group
repeatable(group g)3725 repeatable(group g) {
3726 return g.repeatable(true);
3727 }
3728
3729
3730
3731 /*************************************************************************//**
3732 *
3733 * @brief makes a group of parameters and/or groups
3734 * that is repeatable as a whole
3735 * Note that a repeatable group consisting entirely of non-blocking
3736 * children is equivalent to a non-repeatable group of
3737 * repeatable children.
3738 *
3739 *****************************************************************************/
3740 template<class P2, class... Ps>
3741 inline group
repeatable(parameter p1,P2 p2,Ps...ps)3742 repeatable(parameter p1, P2 p2, Ps... ps)
3743 {
3744 return group{std::move(p1), std::move(p2),
3745 std::move(ps)...}.repeatable(true);
3746 }
3747
3748 template<class P2, class... Ps>
3749 inline group
repeatable(group p1,P2 p2,Ps...ps)3750 repeatable(group p1, P2 p2, Ps... ps)
3751 {
3752 return group{std::move(p1), std::move(p2),
3753 std::move(ps)...}.repeatable(true);
3754 }
3755
3756
3757
3758 /*************************************************************************//**
3759 *
3760 * @brief makes a parameter greedy (match with top priority)
3761 *
3762 *****************************************************************************/
3763 inline parameter
greedy(parameter p)3764 greedy(parameter p) {
3765 return p.greedy(true);
3766 }
3767
3768 inline parameter
operator !(parameter p)3769 operator ! (parameter p) {
3770 return greedy(p);
3771 }
3772
3773
3774
3775 /*************************************************************************//**
3776 *
3777 * @brief recursively prepends a prefix to all flags
3778 *
3779 *****************************************************************************/
3780 inline parameter&&
with_prefix(const arg_string & prefix,parameter && p)3781 with_prefix(const arg_string& prefix, parameter&& p) {
3782 return std::move(with_prefix(prefix, p));
3783 }
3784
3785
3786 //-------------------------------------------------------------------
3787 inline group&
with_prefix(const arg_string & prefix,group & g)3788 with_prefix(const arg_string& prefix, group& g)
3789 {
3790 for(auto& p : g) {
3791 if(p.is_group()) {
3792 with_prefix(prefix, p.as_group());
3793 } else {
3794 with_prefix(prefix, p.as_param());
3795 }
3796 }
3797 return g;
3798 }
3799
3800
3801 inline group&&
with_prefix(const arg_string & prefix,group && params)3802 with_prefix(const arg_string& prefix, group&& params)
3803 {
3804 return std::move(with_prefix(prefix, params));
3805 }
3806
3807
3808 template<class Param, class... Params>
3809 inline group
with_prefix(arg_string prefix,Param && param,Params &&...params)3810 with_prefix(arg_string prefix, Param&& param, Params&&... params)
3811 {
3812 return with_prefix(prefix, group{std::forward<Param>(param),
3813 std::forward<Params>(params)...});
3814 }
3815
3816
3817
3818 /*************************************************************************//**
3819 *
3820 * @brief recursively prepends a prefix to all flags
3821 *
3822 * @param shortpfx : used for single-letter flags
3823 * @param longpfx : used for flags with length > 1
3824 *
3825 *****************************************************************************/
3826 inline parameter&&
with_prefixes_short_long(const arg_string & shortpfx,const arg_string & longpfx,parameter && p)3827 with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx,
3828 parameter&& p)
3829 {
3830 return std::move(with_prefixes_short_long(shortpfx, longpfx, p));
3831 }
3832
3833
3834 //-------------------------------------------------------------------
3835 inline group&
with_prefixes_short_long(const arg_string & shortFlagPrefix,const arg_string & longFlagPrefix,group & g)3836 with_prefixes_short_long(const arg_string& shortFlagPrefix,
3837 const arg_string& longFlagPrefix,
3838 group& g)
3839 {
3840 for(auto& p : g) {
3841 if(p.is_group()) {
3842 with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group());
3843 } else {
3844 with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param());
3845 }
3846 }
3847 return g;
3848 }
3849
3850
3851 inline group&&
with_prefixes_short_long(const arg_string & shortFlagPrefix,const arg_string & longFlagPrefix,group && params)3852 with_prefixes_short_long(const arg_string& shortFlagPrefix,
3853 const arg_string& longFlagPrefix,
3854 group&& params)
3855 {
3856 return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
3857 params));
3858 }
3859
3860
3861 template<class Param, class... Params>
3862 inline group
with_prefixes_short_long(const arg_string & shortFlagPrefix,const arg_string & longFlagPrefix,Param && param,Params &&...params)3863 with_prefixes_short_long(const arg_string& shortFlagPrefix,
3864 const arg_string& longFlagPrefix,
3865 Param&& param, Params&&... params)
3866 {
3867 return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
3868 group{std::forward<Param>(param),
3869 std::forward<Params>(params)...});
3870 }
3871
3872
3873
3874
3875
3876
3877
3878
3879 /*************************************************************************//**
3880 *
3881 * @brief parsing implementation details
3882 *
3883 *****************************************************************************/
3884
3885 namespace detail {
3886
3887
3888 /*************************************************************************//**
3889 *
3890 * @brief DFS traverser that keeps track of 'scopes'
3891 * scope = all parameters that are either bounded by
3892 * two blocking parameters on the same depth level
3893 * or the beginning/end of the outermost group
3894 *
3895 *****************************************************************************/
3896 class scoped_dfs_traverser
3897 {
3898 public:
3899 using dfs_traverser = group::depth_first_traverser;
3900
3901 scoped_dfs_traverser() = default;
3902
3903 explicit
scoped_dfs_traverser(const group & g)3904 scoped_dfs_traverser(const group& g):
3905 pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{},
3906 curMatched_{false}, ignoreBlocks_{false},
3907 repeatGroupStarted_{false}, repeatGroupContinues_{false}
3908 {}
3909
base() const3910 const dfs_traverser& base() const noexcept { return pos_; }
last_match() const3911 const dfs_traverser& last_match() const noexcept { return lastMatch_; }
3912
parent() const3913 const group& parent() const noexcept { return pos_.parent(); }
repeat_group() const3914 const group* repeat_group() const noexcept { return pos_.repeat_group(); }
join_group() const3915 const group* join_group() const noexcept { return pos_.join_group(); }
3916
operator ->() const3917 const pattern* operator ->() const noexcept { return pos_.operator->(); }
operator *() const3918 const pattern& operator *() const noexcept { return *pos_; }
3919
ptr() const3920 const pattern* ptr() const noexcept { return pos_.operator->(); }
3921
operator bool() const3922 explicit operator bool() const noexcept { return bool(pos_); }
3923
joinable() const3924 bool joinable() const noexcept { return pos_.joinable(); }
common_flag_prefix() const3925 arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); }
3926
ignore_blocking(bool yes)3927 void ignore_blocking(bool yes) { ignoreBlocks_ = yes; }
3928
invalidate()3929 void invalidate() { pos_.invalidate(); curMatched_ = false; }
matched() const3930 bool matched() const noexcept { return curMatched_; }
3931
start_of_repeat_group() const3932 bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; }
3933
3934 //-----------------------------------------------------
3935 scoped_dfs_traverser&
next_sibling()3936 next_sibling() { pos_.next_sibling(); return *this; }
3937
3938 scoped_dfs_traverser&
next_alternative()3939 next_alternative() { pos_.next_alternative(); return *this; }
3940
3941 scoped_dfs_traverser&
next_after_siblings()3942 next_after_siblings() { pos_.next_after_siblings(); return *this; }
3943
3944 //-----------------------------------------------------
3945 scoped_dfs_traverser&
operator ++()3946 operator ++ ()
3947 {
3948 if(!pos_) return *this;
3949
3950 if(pos_.is_last_in_path()) {
3951 return_to_outermost_scope();
3952 return *this;
3953 }
3954
3955 //current pattern can block if it didn't match already
3956 if(!ignoreBlocks_ && !matched()) {
3957 //current group can block if we didn't have any match in it
3958 if(pos_.is_last_in_group() && pos_.parent().blocking()
3959 && (!posAfterLastMatch_ || &(posAfterLastMatch_.parent()) != &(pos_.parent())))
3960 {
3961 //ascend to parent's level
3962 ++pos_;
3963 //skip all siblings of parent group
3964 pos_.next_after_siblings();
3965 if(!pos_) return_to_outermost_scope();
3966 }
3967 else if(pos_->blocking() && !pos_->is_group()) {
3968 if(pos_.parent().exclusive()) { //is_alternative(pos_.level())) {
3969 pos_.next_alternative();
3970 } else {
3971 //no match => skip siblings of blocking param
3972 pos_.next_after_siblings();
3973 }
3974 if(!pos_) return_to_outermost_scope();
3975 } else {
3976 ++pos_;
3977 }
3978 } else {
3979 ++pos_;
3980 }
3981 check_if_left_scope();
3982 return *this;
3983 }
3984
3985 //-----------------------------------------------------
next_after_match(scoped_dfs_traverser match)3986 void next_after_match(scoped_dfs_traverser match)
3987 {
3988 if(!match || ignoreBlocks_) return;
3989
3990 check_repeat_group_start(match);
3991
3992 lastMatch_ = match.base();
3993
3994 if(!match->blocking() && match.base().parent().blocking()) {
3995 match.pos_.back_to_parent();
3996 }
3997
3998 //if match is not in current position & current position is blocking
3999 //=> current position has to be advanced by one so that it is
4000 //no longer reachable within current scope
4001 //(can happen for repeatable, blocking parameters)
4002 if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling();
4003
4004 if(match->blocking()) {
4005 if(match.pos_.is_alternative()) {
4006 //discard other alternatives
4007 match.pos_.skip_alternatives();
4008 }
4009
4010 if(is_last_in_current_scope(match.pos_)) {
4011 //if current param is not repeatable -> back to previous scope
4012 if(!match->repeatable() && !match->is_group()) {
4013 curMatched_ = false;
4014 pos_ = std::move(match.pos_);
4015 if(!scopes_.empty()) pos_.undo(scopes_.top());
4016 }
4017 else { //stay at match position
4018 curMatched_ = true;
4019 pos_ = std::move(match.pos_);
4020 }
4021 }
4022 else { //not last in current group
4023 //if current param is not repeatable, go directly to next
4024 if(!match->repeatable() && !match->is_group()) {
4025 curMatched_ = false;
4026 ++match.pos_;
4027 } else {
4028 curMatched_ = true;
4029 }
4030
4031 if(match.pos_.level() > pos_.level()) {
4032 scopes_.push(pos_.undo_point());
4033 pos_ = std::move(match.pos_);
4034 }
4035 else if(match.pos_.level() < pos_.level()) {
4036 return_to_level(match.pos_.level());
4037 }
4038 else {
4039 pos_ = std::move(match.pos_);
4040 }
4041 }
4042 posAfterLastMatch_ = pos_;
4043 }
4044 else {
4045 if(match.pos_.level() < pos_.level()) {
4046 return_to_level(match.pos_.level());
4047 }
4048 posAfterLastMatch_ = pos_;
4049 }
4050 repeatGroupContinues_ = repeat_group_continues();
4051 }
4052
4053 private:
4054 //-----------------------------------------------------
is_last_in_current_scope(const dfs_traverser & pos)4055 bool is_last_in_current_scope(const dfs_traverser& pos)
4056 {
4057 if(scopes_.empty()) return pos.is_last_in_path();
4058 //check if we would leave the current scope on ++
4059 auto p = pos;
4060 ++p;
4061 return p.level() < scopes_.top().level();
4062 }
4063
4064 //-----------------------------------------------------
check_repeat_group_start(const scoped_dfs_traverser & newMatch)4065 void check_repeat_group_start(const scoped_dfs_traverser& newMatch)
4066 {
4067 const auto newrg = newMatch.repeat_group();
4068 if(!newrg) {
4069 repeatGroupStarted_ = false;
4070 }
4071 else if(lastMatch_.repeat_group() != newrg) {
4072 repeatGroupStarted_ = true;
4073 }
4074 else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) {
4075 repeatGroupStarted_ = true;
4076 }
4077 else {
4078 //special case: repeat group is outermost group
4079 //=> we can never really 'leave' and 'reenter' it
4080 //but if the current scope is the first element, then we are
4081 //conceptually at a position 'before' the group
4082 repeatGroupStarted_ = scopes_.empty() || (
4083 newrg == pos_.root() &&
4084 scopes_.top().param() == &(*pos_.root()->begin()) );
4085 }
4086 repeatGroupContinues_ = repeatGroupStarted_;
4087 }
4088
4089 //-----------------------------------------------------
repeat_group_continues()4090 bool repeat_group_continues()
4091 {
4092 if(!repeatGroupContinues_) return false;
4093 const auto curRepGroup = pos_.repeat_group();
4094 if(!curRepGroup) return false;
4095 if(curRepGroup != lastMatch_.repeat_group()) return false;
4096 if(!posAfterLastMatch_) return false;
4097 return true;
4098 }
4099
4100 //-----------------------------------------------------
check_if_left_scope()4101 void check_if_left_scope()
4102 {
4103 if(posAfterLastMatch_) {
4104 if(pos_.level() < posAfterLastMatch_.level()) {
4105 while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) {
4106 pos_.undo(scopes_.top());
4107 scopes_.pop();
4108 }
4109 posAfterLastMatch_.invalidate();
4110 }
4111 }
4112 while(!scopes_.empty() && scopes_.top().level() > pos_.level()) {
4113 pos_.undo(scopes_.top());
4114 scopes_.pop();
4115 }
4116 repeatGroupContinues_ = repeat_group_continues();
4117 }
4118
4119 //-----------------------------------------------------
return_to_outermost_scope()4120 void return_to_outermost_scope()
4121 {
4122 posAfterLastMatch_.invalidate();
4123
4124 if(scopes_.empty()) {
4125 pos_.invalidate();
4126 repeatGroupContinues_ = false;
4127 return;
4128 }
4129
4130 while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) {
4131 pos_.undo(scopes_.top());
4132 scopes_.pop();
4133 }
4134 while(!scopes_.empty()) scopes_.pop();
4135
4136 repeatGroupContinues_ = repeat_group_continues();
4137 }
4138
4139 //-----------------------------------------------------
return_to_level(int level)4140 void return_to_level(int level)
4141 {
4142 if(pos_.level() <= level) return;
4143 while(!scopes_.empty() && pos_.level() > level) {
4144 pos_.undo(scopes_.top());
4145 scopes_.pop();
4146 }
4147 };
4148
4149 dfs_traverser pos_;
4150 dfs_traverser lastMatch_;
4151 dfs_traverser posAfterLastMatch_;
4152 std::stack<dfs_traverser::memento> scopes_;
4153 bool curMatched_ = false;
4154 bool ignoreBlocks_ = false;
4155 bool repeatGroupStarted_ = false;
4156 bool repeatGroupContinues_ = false;
4157 };
4158
4159
4160
4161
4162 /*****************************************************************************
4163 *
4164 * some parameter property predicates
4165 *
4166 *****************************************************************************/
4167 struct select_all {
operator ()clipp::detail::select_all4168 bool operator () (const parameter&) const noexcept { return true; }
4169 };
4170
4171 struct select_flags {
operator ()clipp::detail::select_flags4172 bool operator () (const parameter& p) const noexcept {
4173 return !p.flags().empty();
4174 }
4175 };
4176
4177 struct select_values {
operator ()clipp::detail::select_values4178 bool operator () (const parameter& p) const noexcept {
4179 return p.flags().empty();
4180 }
4181 };
4182
4183
4184
4185 /*************************************************************************//**
4186 *
4187 * @brief result of a matching operation
4188 *
4189 *****************************************************************************/
4190 class match_t {
4191 public:
4192 match_t() = default;
match_t(arg_string s,scoped_dfs_traverser p)4193 match_t(arg_string s, scoped_dfs_traverser p):
4194 str_{std::move(s)}, pos_{std::move(p)}
4195 {}
4196
str() const4197 const arg_string& str() const noexcept { return str_; }
pos() const4198 const scoped_dfs_traverser& pos() const noexcept { return pos_; }
4199
operator bool() const4200 explicit operator bool() const noexcept { return !str_.empty(); }
4201
4202 private:
4203 arg_string str_;
4204 scoped_dfs_traverser pos_;
4205 };
4206
4207
4208
4209 /*************************************************************************//**
4210 *
4211 * @brief finds the first parameter that matches a given string;
4212 * candidate parameters are traversed using a scoped DFS traverser
4213 *
4214 *****************************************************************************/
4215 template<class ParamSelector>
4216 match_t
full_match(scoped_dfs_traverser pos,const arg_string & arg,const ParamSelector & select)4217 full_match(scoped_dfs_traverser pos, const arg_string& arg,
4218 const ParamSelector& select)
4219 {
4220 if(arg.empty()) return match_t{};
4221
4222 while(pos) {
4223 if(pos->is_param()) {
4224 const auto& param = pos->as_param();
4225 if(select(param)) {
4226 const auto match = param.match(arg);
4227 if(match && match.length() == arg.size()) {
4228 return match_t{arg, std::move(pos)};
4229 }
4230 }
4231 }
4232 ++pos;
4233 }
4234 return match_t{};
4235 }
4236
4237
4238
4239 /*************************************************************************//**
4240 *
4241 * @brief finds the first parameter that matches any (non-empty) prefix
4242 * of a given string;
4243 * candidate parameters are traversed using a scoped DFS traverser
4244 *
4245 *****************************************************************************/
4246 template<class ParamSelector>
4247 match_t
prefix_match(scoped_dfs_traverser pos,const arg_string & arg,const ParamSelector & select)4248 prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
4249 const ParamSelector& select)
4250 {
4251 if(arg.empty()) return match_t{};
4252
4253 while(pos) {
4254 if(pos->is_param()) {
4255 const auto& param = pos->as_param();
4256 if(select(param)) {
4257 const auto match = param.match(arg);
4258 if(match.prefix()) {
4259 if(match.length() == arg.size()) {
4260 return match_t{arg, std::move(pos)};
4261 }
4262 else {
4263 return match_t{arg.substr(match.at(), match.length()),
4264 std::move(pos)};
4265 }
4266 }
4267 }
4268 }
4269 ++pos;
4270 }
4271 return match_t{};
4272 }
4273
4274
4275
4276 /*************************************************************************//**
4277 *
4278 * @brief finds the first parameter that partially matches a given string;
4279 * candidate parameters are traversed using a scoped DFS traverser
4280 *
4281 *****************************************************************************/
4282 template<class ParamSelector>
4283 match_t
partial_match(scoped_dfs_traverser pos,const arg_string & arg,const ParamSelector & select)4284 partial_match(scoped_dfs_traverser pos, const arg_string& arg,
4285 const ParamSelector& select)
4286 {
4287 if(arg.empty()) return match_t{};
4288
4289 while(pos) {
4290 if(pos->is_param()) {
4291 const auto& param = pos->as_param();
4292 if(select(param)) {
4293 const auto match = param.match(arg);
4294 if(match) {
4295 return match_t{arg.substr(match.at(), match.length()),
4296 std::move(pos)};
4297 }
4298 }
4299 }
4300 ++pos;
4301 }
4302 return match_t{};
4303 }
4304
4305 } //namespace detail
4306
4307
4308
4309
4310
4311
4312 /***************************************************************//**
4313 *
4314 * @brief default command line arguments parser
4315 *
4316 *******************************************************************/
4317 class parser
4318 {
4319 public:
4320 using dfs_traverser = group::depth_first_traverser;
4321 using scoped_dfs_traverser = detail::scoped_dfs_traverser;
4322
4323
4324 /*****************************************************//**
4325 * @brief arg -> parameter mapping
4326 *********************************************************/
4327 class arg_mapping {
4328 public:
4329 friend class parser;
4330
4331 explicit
arg_mapping(arg_index idx,arg_string s,const dfs_traverser & match)4332 arg_mapping(arg_index idx, arg_string s,
4333 const dfs_traverser& match)
4334 :
4335 index_{idx}, arg_{std::move(s)}, match_{match},
4336 repeat_{0}, startsRepeatGroup_{false},
4337 blocked_{false}, conflict_{false}
4338 {}
4339
4340 explicit
arg_mapping(arg_index idx,arg_string s)4341 arg_mapping(arg_index idx, arg_string s) :
4342 index_{idx}, arg_{std::move(s)}, match_{},
4343 repeat_{0}, startsRepeatGroup_{false},
4344 blocked_{false}, conflict_{false}
4345 {}
4346
index() const4347 arg_index index() const noexcept { return index_; }
arg() const4348 const arg_string& arg() const noexcept { return arg_; }
4349
param() const4350 const parameter* param() const noexcept {
4351 return match_ && match_->is_param()
4352 ? &(match_->as_param()) : nullptr;
4353 }
4354
repeat() const4355 std::size_t repeat() const noexcept { return repeat_; }
4356
blocked() const4357 bool blocked() const noexcept { return blocked_; }
conflict() const4358 bool conflict() const noexcept { return conflict_; }
4359
bad_repeat() const4360 bool bad_repeat() const noexcept {
4361 if(!param()) return false;
4362 return repeat_ > 0 && !param()->repeatable()
4363 && !match_.repeat_group();
4364 }
4365
any_error() const4366 bool any_error() const noexcept {
4367 return !match_ || blocked() || conflict() || bad_repeat();
4368 }
4369
4370 private:
4371 arg_index index_;
4372 arg_string arg_;
4373 dfs_traverser match_;
4374 std::size_t repeat_;
4375 bool startsRepeatGroup_;
4376 bool blocked_;
4377 bool conflict_;
4378 };
4379
4380 /*****************************************************//**
4381 * @brief references a non-matched, required parameter
4382 *********************************************************/
4383 class missing_event {
4384 public:
4385 explicit
missing_event(const parameter * p,arg_index after)4386 missing_event(const parameter* p, arg_index after):
4387 param_{p}, aftIndex_{after}
4388 {}
4389
param() const4390 const parameter* param() const noexcept { return param_; }
4391
after_index() const4392 arg_index after_index() const noexcept { return aftIndex_; }
4393
4394 private:
4395 const parameter* param_;
4396 arg_index aftIndex_;
4397 };
4398
4399 //-----------------------------------------------------
4400 using missing_events = std::vector<missing_event>;
4401 using arg_mappings = std::vector<arg_mapping>;
4402
4403
4404 private:
4405 struct miss_candidate {
miss_candidateclipp::parser::miss_candidate4406 miss_candidate(dfs_traverser p, arg_index idx,
4407 bool firstInRepeatGroup = false):
4408 pos{std::move(p)}, index{idx},
4409 startsRepeatGroup{firstInRepeatGroup}
4410 {}
4411
4412 dfs_traverser pos;
4413 arg_index index;
4414 bool startsRepeatGroup;
4415 };
4416 using miss_candidates = std::vector<miss_candidate>;
4417
4418
4419 public:
4420 //---------------------------------------------------------------
4421 /** @brief initializes parser with a command line interface
4422 * @param offset = argument index offset used for reports
4423 * */
4424 explicit
parser(const group & root,arg_index offset=0)4425 parser(const group& root, arg_index offset = 0):
4426 root_{&root}, pos_{root},
4427 index_{offset-1}, eaten_{0},
4428 args_{}, missCand_{}, blocked_{false}
4429 {
4430 for_each_potential_miss(dfs_traverser{root},
__anond2d1c15e1302(const dfs_traverser& p)4431 [this](const dfs_traverser& p){
4432 missCand_.emplace_back(p, index_);
4433 });
4434 }
4435
4436
4437 //---------------------------------------------------------------
4438 /** @brief processes one command line argument */
operator ()(const arg_string & arg)4439 bool operator() (const arg_string& arg)
4440 {
4441 ++eaten_;
4442 ++index_;
4443
4444 if(!valid() || arg.empty()) return false;
4445
4446 if(!blocked_ && try_match(arg)) return true;
4447
4448 if(try_match_blocked(arg)) return false;
4449
4450 //skipping of blocking & required patterns is not allowed
4451 if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) {
4452 blocked_ = true;
4453 }
4454
4455 add_nomatch(arg);
4456 return false;
4457 }
4458
4459
4460 //---------------------------------------------------------------
4461 /** @brief returns range of argument -> parameter mappings */
args() const4462 const arg_mappings& args() const {
4463 return args_;
4464 }
4465
4466 /** @brief returns list of missing events */
missed() const4467 missing_events missed() const {
4468 missing_events misses;
4469 misses.reserve(missCand_.size());
4470 for(auto i = missCand_.begin(); i != missCand_.end(); ++i) {
4471 misses.emplace_back(&(i->pos->as_param()), i->index);
4472 }
4473 return misses;
4474 }
4475
4476 /** @brief returns number of processed command line arguments */
parse_count() const4477 arg_index parse_count() const noexcept { return eaten_; }
4478
4479 /** @brief returns false if previously processed command line arguments
4480 * lead to an invalid / inconsistent parsing result
4481 */
valid() const4482 bool valid() const noexcept { return bool(pos_); }
4483
4484 /** @brief returns false if previously processed command line arguments
4485 * lead to an invalid / inconsistent parsing result
4486 */
operator bool() const4487 explicit operator bool() const noexcept { return valid(); }
4488
4489
4490 private:
4491 //---------------------------------------------------------------
4492 using match_t = detail::match_t;
4493
4494
4495 //---------------------------------------------------------------
4496 /** @brief try to match argument with unreachable parameter */
try_match_blocked(const arg_string & arg)4497 bool try_match_blocked(const arg_string& arg)
4498 {
4499 //try to match ahead (using temporary parser)
4500 if(pos_) {
4501 auto ahead = *this;
4502 if(try_match_blocked(std::move(ahead), arg)) return true;
4503 }
4504
4505 //try to match from the beginning (using temporary parser)
4506 if(root_) {
4507 parser all{*root_, index_+1};
4508 if(try_match_blocked(std::move(all), arg)) return true;
4509 }
4510
4511 return false;
4512 }
4513
4514 //---------------------------------------------------------------
try_match_blocked(parser && parse,const arg_string & arg)4515 bool try_match_blocked(parser&& parse, const arg_string& arg)
4516 {
4517 const auto nold = int(parse.args_.size());
4518
4519 parse.pos_.ignore_blocking(true);
4520
4521 if(!parse.try_match(arg)) return false;
4522
4523 for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) {
4524 args_.push_back(*i);
4525 args_.back().blocked_ = true;
4526 }
4527 return true;
4528 }
4529
4530 //---------------------------------------------------------------
4531 /** @brief try to find a parameter/pattern that matches 'arg' */
try_match(const arg_string & arg)4532 bool try_match(const arg_string& arg)
4533 {
4534 //match greedy parameters before everything else
4535 if(pos_->is_param() && pos_->blocking() && pos_->as_param().greedy()) {
4536 const auto match = pos_->as_param().match(arg);
4537 if(match && match.length() == arg.size()) {
4538 add_match(detail::match_t{arg,pos_});
4539 return true;
4540 }
4541 }
4542
4543 //try flags first (alone, joinable or strict sequence)
4544 if(try_match_full(arg, detail::select_flags{})) return true;
4545 if(try_match_joined_flags(arg)) return true;
4546 if(try_match_joined_sequence(arg, detail::select_flags{})) return true;
4547 //try value params (alone or strict sequence)
4548 if(try_match_full(arg, detail::select_values{})) return true;
4549 if(try_match_joined_sequence(arg, detail::select_all{})) return true;
4550 //try joinable params + values in any order
4551 if(try_match_joined_params(arg)) return true;
4552 return false;
4553 }
4554
4555 //---------------------------------------------------------------
4556 /**
4557 * @brief try to match full argument
4558 * @param select : predicate that candidate parameters must satisfy
4559 */
4560 template<class ParamSelector>
try_match_full(const arg_string & arg,const ParamSelector & select)4561 bool try_match_full(const arg_string& arg, const ParamSelector& select)
4562 {
4563 auto match = detail::full_match(pos_, arg, select);
4564 if(!match) return false;
4565 add_match(match);
4566 return true;
4567 }
4568
4569 //---------------------------------------------------------------
4570 /**
4571 * @brief try to match argument as blocking sequence of parameters
4572 * @param select : predicate that a parameter matching the prefix of
4573 * 'arg' must satisfy
4574 */
4575 template<class ParamSelector>
try_match_joined_sequence(arg_string arg,const ParamSelector & acceptFirst)4576 bool try_match_joined_sequence(arg_string arg,
4577 const ParamSelector& acceptFirst)
4578 {
4579 auto fstMatch = detail::prefix_match(pos_, arg, acceptFirst);
4580
4581 if(!fstMatch) return false;
4582
4583 if(fstMatch.str().size() == arg.size()) {
4584 add_match(fstMatch);
4585 return true;
4586 }
4587
4588 if(!fstMatch.pos()->blocking()) return false;
4589
4590 auto pos = fstMatch.pos();
4591 pos.ignore_blocking(true);
4592 const auto parent = &pos.parent();
4593 if(!pos->repeatable()) ++pos;
4594
4595 arg.erase(0, fstMatch.str().size());
4596 std::vector<match_t> matches { std::move(fstMatch) };
4597
4598 while(!arg.empty() && pos &&
4599 pos->blocking() && pos->is_param() &&
4600 (&pos.parent() == parent))
4601 {
4602 auto match = pos->as_param().match(arg);
4603
4604 if(match.prefix()) {
4605 matches.emplace_back(arg.substr(0,match.length()), pos);
4606 arg.erase(0, match.length());
4607 if(!pos->repeatable()) ++pos;
4608 }
4609 else {
4610 if(!pos->repeatable()) return false;
4611 ++pos;
4612 }
4613
4614 }
4615 //if arg not fully covered => discard temporary matches
4616 if(!arg.empty() || matches.empty()) return false;
4617
4618 for(const auto& m : matches) add_match(m);
4619 return true;
4620 }
4621
4622 //-----------------------------------------------------
4623 /** @brief try to match 'arg' as a concatenation of joinable flags */
try_match_joined_flags(const arg_string & arg)4624 bool try_match_joined_flags(const arg_string& arg)
4625 {
4626 return find_join_group(pos_, [&](const group& g) {
4627 return try_match_joined(g, arg, detail::select_flags{},
4628 g.common_flag_prefix());
4629 });
4630 }
4631
4632 //---------------------------------------------------------------
4633 /** @brief try to match 'arg' as a concatenation of joinable parameters */
try_match_joined_params(const arg_string & arg)4634 bool try_match_joined_params(const arg_string& arg)
4635 {
4636 return find_join_group(pos_, [&](const group& g) {
4637 return try_match_joined(g, arg, detail::select_all{});
4638 });
4639 }
4640
4641 //-----------------------------------------------------
4642 /** @brief try to match 'arg' as concatenation of joinable parameters
4643 * that are all contaied within one group
4644 */
4645 template<class ParamSelector>
try_match_joined(const group & joinGroup,arg_string arg,const ParamSelector & select,const arg_string & prefix="")4646 bool try_match_joined(const group& joinGroup, arg_string arg,
4647 const ParamSelector& select,
4648 const arg_string& prefix = "")
4649 {
4650 //temporary parser with 'joinGroup' as top-level group
4651 parser parse {joinGroup};
4652 //records temporary matches
4653 std::vector<match_t> matches;
4654
4655 while(!arg.empty()) {
4656 auto match = detail::prefix_match(parse.pos_, arg, select);
4657
4658 if(!match) return false;
4659
4660 arg.erase(0, match.str().size());
4661 //make sure prefix is always present after the first match
4662 //so that, e.g., flags "-a" and "-b" will be found in "-ab"
4663 if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 &&
4664 prefix != match.str())
4665 {
4666 arg.insert(0,prefix);
4667 }
4668
4669 parse.add_match(match);
4670 matches.push_back(std::move(match));
4671 }
4672
4673 if(!arg.empty() || matches.empty()) return false;
4674
4675 if(!parse.missCand_.empty()) return false;
4676 for(const auto& a : parse.args_) if(a.any_error()) return false;
4677
4678 //replay matches onto *this
4679 for(const auto& m : matches) add_match(m);
4680 return true;
4681 }
4682
4683 //-----------------------------------------------------
4684 template<class GroupSelector>
find_join_group(const scoped_dfs_traverser & start,const GroupSelector & accept) const4685 bool find_join_group(const scoped_dfs_traverser& start,
4686 const GroupSelector& accept) const
4687 {
4688 if(start && start.parent().joinable()) {
4689 const auto& g = start.parent();
4690 if(accept(g)) return true;
4691 return false;
4692 }
4693
4694 auto pos = start;
4695 while(pos) {
4696 if(pos->is_group() && pos->as_group().joinable()) {
4697 const auto& g = pos->as_group();
4698 if(accept(g)) return true;
4699 pos.next_sibling();
4700 }
4701 else {
4702 ++pos;
4703 }
4704 }
4705 return false;
4706 }
4707
4708
4709 //---------------------------------------------------------------
add_nomatch(const arg_string & arg)4710 void add_nomatch(const arg_string& arg) {
4711 args_.emplace_back(index_, arg);
4712 }
4713
4714
4715 //---------------------------------------------------------------
add_match(const match_t & match)4716 void add_match(const match_t& match)
4717 {
4718 const auto& pos = match.pos();
4719 if(!pos || !pos->is_param() || match.str().empty()) return;
4720
4721 pos_.next_after_match(pos);
4722
4723 arg_mapping newArg{index_, match.str(), pos.base()};
4724 newArg.repeat_ = occurrences_of(&pos->as_param());
4725 newArg.conflict_ = check_conflicts(pos.base());
4726 newArg.startsRepeatGroup_ = pos_.start_of_repeat_group();
4727 args_.push_back(std::move(newArg));
4728
4729 add_miss_candidates_after(pos);
4730 clean_miss_candidates_for(pos.base());
4731 discard_alternative_miss_candidates(pos.base());
4732
4733 }
4734
4735 //-----------------------------------------------------
check_conflicts(const dfs_traverser & match)4736 bool check_conflicts(const dfs_traverser& match)
4737 {
4738 if(pos_.start_of_repeat_group()) return false;
4739 bool conflict = false;
4740 for(const auto& m : match.stack()) {
4741 if(m.parent->exclusive()) {
4742 for(auto i = args_.rbegin(); i != args_.rend(); ++i) {
4743 if(!i->blocked()) {
4744 for(const auto& c : i->match_.stack()) {
4745 //sibling within same exclusive group => conflict
4746 if(c.parent == m.parent && c.cur != m.cur) {
4747 conflict = true;
4748 i->conflict_ = true;
4749 }
4750 }
4751 }
4752 //check for conflicts only within current repeat cycle
4753 if(i->startsRepeatGroup_) break;
4754 }
4755 }
4756 }
4757 return conflict;
4758 }
4759
4760 //-----------------------------------------------------
clean_miss_candidates_for(const dfs_traverser & match)4761 void clean_miss_candidates_for(const dfs_traverser& match)
4762 {
4763 auto i = std::find_if(missCand_.rbegin(), missCand_.rend(),
4764 [&](const miss_candidate& m) {
4765 return &(*m.pos) == &(*match);
4766 });
4767
4768 if(i != missCand_.rend()) {
4769 missCand_.erase(prev(i.base()));
4770 }
4771 }
4772
4773 //-----------------------------------------------------
discard_alternative_miss_candidates(const dfs_traverser & match)4774 void discard_alternative_miss_candidates(const dfs_traverser& match)
4775 {
4776 if(missCand_.empty()) return;
4777 //find out, if miss candidate is sibling of one of the same
4778 //alternative groups that the current match is a member of
4779 //if so, we can discard the miss
4780
4781 //go through all exclusive groups of matching pattern
4782 for(const auto& m : match.stack()) {
4783 if(m.parent->exclusive()) {
4784 for(auto i = int(missCand_.size())-1; i >= 0; --i) {
4785 bool removed = false;
4786 for(const auto& c : missCand_[i].pos.stack()) {
4787 //sibling within same exclusive group => discard
4788 if(c.parent == m.parent && c.cur != m.cur) {
4789 missCand_.erase(missCand_.begin() + i);
4790 if(missCand_.empty()) return;
4791 removed = true;
4792 break;
4793 }
4794 }
4795 //remove miss candidates only within current repeat cycle
4796 if(i > 0 && removed) {
4797 if(missCand_[i-1].startsRepeatGroup) break;
4798 } else {
4799 if(missCand_[i].startsRepeatGroup) break;
4800 }
4801 }
4802 }
4803 }
4804 }
4805
4806 //-----------------------------------------------------
add_miss_candidates_after(const scoped_dfs_traverser & match)4807 void add_miss_candidates_after(const scoped_dfs_traverser& match)
4808 {
4809 auto npos = match.base();
4810 if(npos.is_alternative()) npos.skip_alternatives();
4811 ++npos;
4812 //need to add potential misses if:
4813 //either new repeat group was started
4814 const auto newRepGroup = match.repeat_group();
4815 if(newRepGroup) {
4816 if(pos_.start_of_repeat_group()) {
4817 for_each_potential_miss(std::move(npos),
4818 [&,this](const dfs_traverser& pos) {
4819 //only add candidates within repeat group
4820 if(newRepGroup == pos.repeat_group()) {
4821 missCand_.emplace_back(pos, index_, true);
4822 }
4823 });
4824 }
4825 }
4826 //... or an optional blocking param was hit
4827 else if(match->blocking() && !match->required() &&
4828 npos.level() >= match.base().level())
4829 {
4830 for_each_potential_miss(std::move(npos),
4831 [&,this](const dfs_traverser& pos) {
4832 //only add new candidates
4833 if(std::find_if(missCand_.begin(), missCand_.end(),
4834 [&](const miss_candidate& c){
4835 return &(*c.pos) == &(*pos);
4836 }) == missCand_.end())
4837 {
4838 missCand_.emplace_back(pos, index_);
4839 }
4840 });
4841 }
4842
4843 }
4844
4845 //-----------------------------------------------------
4846 template<class Action>
4847 static void
for_each_potential_miss(dfs_traverser pos,Action && action)4848 for_each_potential_miss(dfs_traverser pos, Action&& action)
4849 {
4850 const auto level = pos.level();
4851 while(pos && pos.level() >= level) {
4852 if(pos->is_group() ) {
4853 const auto& g = pos->as_group();
4854 if(g.all_optional() || (g.exclusive() && g.any_optional())) {
4855 pos.next_sibling();
4856 } else {
4857 ++pos;
4858 }
4859 } else { //param
4860 if(pos->required()) {
4861 action(pos);
4862 ++pos;
4863 } else if(pos->blocking()) { //optional + blocking
4864 pos.next_after_siblings();
4865 } else {
4866 ++pos;
4867 }
4868 }
4869 }
4870 }
4871
4872
4873 //---------------------------------------------------------------
occurrences_of(const parameter * p) const4874 std::size_t occurrences_of(const parameter* p) const
4875 {
4876 auto i = std::find_if(args_.rbegin(), args_.rend(),
4877 [p](const arg_mapping& a){ return a.param() == p; });
4878
4879 if(i != args_.rend()) return i->repeat() + 1;
4880 return 0;
4881 }
4882
4883
4884 //---------------------------------------------------------------
4885 const group* root_;
4886 scoped_dfs_traverser pos_;
4887 arg_index index_;
4888 arg_index eaten_;
4889 arg_mappings args_;
4890 miss_candidates missCand_;
4891 bool blocked_;
4892 };
4893
4894
4895
4896
4897 /*************************************************************************//**
4898 *
4899 * @brief contains argument -> parameter mappings
4900 * and missing parameters
4901 *
4902 *****************************************************************************/
4903 class parsing_result
4904 {
4905 public:
4906 using arg_mapping = parser::arg_mapping;
4907 using arg_mappings = parser::arg_mappings;
4908 using missing_event = parser::missing_event;
4909 using missing_events = parser::missing_events;
4910 using iterator = arg_mappings::const_iterator;
4911
4912 //-----------------------------------------------------
4913 /** @brief default: empty redult */
4914 parsing_result() = default;
4915
parsing_result(arg_mappings arg2param,missing_events misses)4916 parsing_result(arg_mappings arg2param, missing_events misses):
4917 arg2param_{std::move(arg2param)}, missing_{std::move(misses)}
4918 {}
4919
4920 //-----------------------------------------------------
4921 /** @brief returns number of arguments that could not be mapped to
4922 * a parameter
4923 */
4924 arg_mappings::size_type
unmapped_args_count() const4925 unmapped_args_count() const noexcept {
4926 return std::count_if(arg2param_.begin(), arg2param_.end(),
4927 [](const arg_mapping& a){ return !a.param(); });
4928 }
4929
4930 /** @brief returns if any argument could only be matched by an
4931 * unreachable parameter
4932 */
any_blocked() const4933 bool any_blocked() const noexcept {
4934 return std::any_of(arg2param_.begin(), arg2param_.end(),
4935 [](const arg_mapping& a){ return a.blocked(); });
4936 }
4937
4938 /** @brief returns if any argument matched more than one parameter
4939 * that were mutually exclusive */
any_conflict() const4940 bool any_conflict() const noexcept {
4941 return std::any_of(arg2param_.begin(), arg2param_.end(),
4942 [](const arg_mapping& a){ return a.conflict(); });
4943 }
4944
4945 /** @brief returns if any parameter matched repeatedly although
4946 * it was not allowed to */
any_bad_repeat() const4947 bool any_bad_repeat() const noexcept {
4948 return std::any_of(arg2param_.begin(), arg2param_.end(),
4949 [](const arg_mapping& a){ return a.bad_repeat(); });
4950 }
4951
4952 /** @brief returns true if any parsing error / violation of the
4953 * command line interface definition occured */
any_error() const4954 bool any_error() const noexcept {
4955 return unmapped_args_count() > 0 || !missing().empty() ||
4956 any_blocked() || any_conflict() || any_bad_repeat();
4957 }
4958
4959 /** @brief returns true if no parsing error / violation of the
4960 * command line interface definition occured */
operator bool() const4961 explicit operator bool() const noexcept { return !any_error(); }
4962
4963 /** @brief access to range of missing parameter match events */
missing() const4964 const missing_events& missing() const noexcept { return missing_; }
4965
4966 /** @brief returns non-mutating iterator to position of
4967 * first argument -> parameter mapping */
begin() const4968 iterator begin() const noexcept { return arg2param_.begin(); }
4969 /** @brief returns non-mutating iterator to position one past the
4970 * last argument -> parameter mapping */
end() const4971 iterator end() const noexcept { return arg2param_.end(); }
4972
4973 private:
4974 //-----------------------------------------------------
4975 arg_mappings arg2param_;
4976 missing_events missing_;
4977 };
4978
4979
4980
4981
4982 namespace detail {
4983 namespace {
4984
4985 /*************************************************************************//**
4986 *
4987 * @brief correct some common problems
4988 * does not - and MUST NOT - change the number of arguments
4989 * (no insertions or deletions allowed)
4990 *
4991 *****************************************************************************/
sanitize_args(arg_list & args)4992 void sanitize_args(arg_list& args)
4993 {
4994 //e.g. {"-o12", ".34"} -> {"-o", "12.34"}
4995
4996 if(args.empty()) return;
4997
4998 for(auto i = begin(args)+1; i != end(args); ++i) {
4999 if(i != begin(args) && i->size() > 1 &&
5000 i->find('.') == 0 && std::isdigit((*i)[1]) )
5001 {
5002 //find trailing digits in previous arg
5003 using std::prev;
5004 auto& prv = *prev(i);
5005 auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(),
5006 [](arg_string::value_type c){
5007 return std::isdigit(c);
5008 }).base();
5009
5010 //handle leading sign
5011 if(fstDigit > prv.begin() &&
5012 (*prev(fstDigit) == '+' || *prev(fstDigit) == '-'))
5013 {
5014 --fstDigit;
5015 }
5016
5017 //prepend digits from previous arg
5018 i->insert(begin(*i), fstDigit, end(prv));
5019
5020 //erase digits in previous arg
5021 prv.erase(fstDigit, end(prv));
5022 }
5023 }
5024 }
5025
5026
5027
5028 /*************************************************************************//**
5029 *
5030 * @brief executes actions based on a parsing result
5031 *
5032 *****************************************************************************/
execute_actions(const parsing_result & res)5033 void execute_actions(const parsing_result& res)
5034 {
5035 for(const auto& m : res) {
5036 if(m.param()) {
5037 const auto& param = *(m.param());
5038
5039 if(m.repeat() > 0) param.notify_repeated(m.index());
5040 if(m.blocked()) param.notify_blocked(m.index());
5041 if(m.conflict()) param.notify_conflict(m.index());
5042 //main action
5043 if(!m.any_error()) param.execute_actions(m.arg());
5044 }
5045 }
5046
5047 for(auto m : res.missing()) {
5048 if(m.param()) m.param()->notify_missing(m.after_index());
5049 }
5050 }
5051
5052
5053
5054 /*************************************************************************//**
5055 *
5056 * @brief parses input args
5057 *
5058 *****************************************************************************/
5059 static parsing_result
parse_args(const arg_list & args,const group & cli,arg_index offset=0)5060 parse_args(const arg_list& args, const group& cli,
5061 arg_index offset = 0)
5062 {
5063 //parse args and store unrecognized arg indices
5064 parser parse{cli, offset};
5065 for(const auto& arg : args) {
5066 parse(arg);
5067 if(!parse.valid()) break;
5068 }
5069
5070 return parsing_result{parse.args(), parse.missed()};
5071 }
5072
5073 /*************************************************************************//**
5074 *
5075 * @brief parses input args & executes actions
5076 *
5077 *****************************************************************************/
5078 static parsing_result
parse_and_execute(const arg_list & args,const group & cli,arg_index offset=0)5079 parse_and_execute(const arg_list& args, const group& cli,
5080 arg_index offset = 0)
5081 {
5082 auto result = parse_args(args, cli, offset);
5083
5084 execute_actions(result);
5085
5086 return result;
5087 }
5088
5089 } //anonymous namespace
5090 } // namespace detail
5091
5092
5093
5094
5095 /*************************************************************************//**
5096 *
5097 * @brief parses vector of arg strings and executes actions
5098 *
5099 *****************************************************************************/
5100 inline parsing_result
parse(arg_list args,const group & cli)5101 parse(arg_list args, const group& cli)
5102 {
5103 detail::sanitize_args(args);
5104 return detail::parse_and_execute(args, cli);
5105 }
5106
5107
5108 /*************************************************************************//**
5109 *
5110 * @brief parses initializer_list of C-style arg strings and executes actions
5111 *
5112 *****************************************************************************/
5113 inline parsing_result
parse(std::initializer_list<const char * > arglist,const group & cli)5114 parse(std::initializer_list<const char*> arglist, const group& cli)
5115 {
5116 arg_list args;
5117 args.reserve(arglist.size());
5118 for(auto a : arglist) {
5119 if(std::strlen(a) > 0) args.push_back(a);
5120 }
5121
5122 return parse(std::move(args), cli);
5123 }
5124
5125
5126 /*************************************************************************//**
5127 *
5128 * @brief parses range of arg strings and executes actions
5129 *
5130 *****************************************************************************/
5131 template<class InputIterator>
5132 inline parsing_result
parse(InputIterator first,InputIterator last,const group & cli)5133 parse(InputIterator first, InputIterator last, const group& cli)
5134 {
5135 return parse(arg_list(first,last), cli);
5136 }
5137
5138
5139 /*************************************************************************//**
5140 *
5141 * @brief parses the standard array of command line arguments; omits argv[0]
5142 *
5143 *****************************************************************************/
5144 inline parsing_result
parse(const int argc,char * argv[],const group & cli,arg_index offset=1)5145 parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
5146 {
5147 arg_list args;
5148 if(offset < argc) args.assign(argv+offset, argv+argc);
5149 detail::sanitize_args(args);
5150 return detail::parse_and_execute(args, cli, offset);
5151 }
5152
5153
5154
5155
5156
5157
5158 /*************************************************************************//**
5159 *
5160 * @brief filter predicate for parameters and groups;
5161 * Can be used to limit documentation generation to parameter subsets.
5162 *
5163 *****************************************************************************/
5164 class param_filter
5165 {
5166 public:
5167 /** @brief only allow parameters with given prefix */
prefix(const arg_string & p)5168 param_filter& prefix(const arg_string& p) noexcept {
5169 prefix_ = p; return *this;
5170 }
5171 /** @brief only allow parameters with given prefix */
prefix(arg_string && p)5172 param_filter& prefix(arg_string&& p) noexcept {
5173 prefix_ = std::move(p); return *this;
5174 }
prefix() const5175 const arg_string& prefix() const noexcept { return prefix_; }
5176
5177 /** @brief only allow parameters with given requirement status */
required(tri t)5178 param_filter& required(tri t) noexcept { required_ = t; return *this; }
required() const5179 tri required() const noexcept { return required_; }
5180
5181 /** @brief only allow parameters with given blocking status */
blocking(tri t)5182 param_filter& blocking(tri t) noexcept { blocking_ = t; return *this; }
blocking() const5183 tri blocking() const noexcept { return blocking_; }
5184
5185 /** @brief only allow parameters with given repeatable status */
repeatable(tri t)5186 param_filter& repeatable(tri t) noexcept { repeatable_ = t; return *this; }
repeatable() const5187 tri repeatable() const noexcept { return repeatable_; }
5188
5189 /** @brief only allow parameters with given docstring status */
has_doc(tri t)5190 param_filter& has_doc(tri t) noexcept { hasDoc_ = t; return *this; }
has_doc() const5191 tri has_doc() const noexcept { return hasDoc_; }
5192
5193
5194 /** @brief returns true, if parameter satisfies all filters */
operator ()(const parameter & p) const5195 bool operator() (const parameter& p) const noexcept {
5196 if(!prefix_.empty()) {
5197 if(!std::any_of(p.flags().begin(), p.flags().end(),
5198 [&](const arg_string& flag){
5199 return str::has_prefix(flag, prefix_);
5200 })) return false;
5201 }
5202 if(required() != p.required()) return false;
5203 if(blocking() != p.blocking()) return false;
5204 if(repeatable() != p.repeatable()) return false;
5205 if(has_doc() != !p.doc().empty()) return false;
5206 return true;
5207 }
5208
5209 private:
5210 arg_string prefix_;
5211 tri required_ = tri::either;
5212 tri blocking_ = tri::either;
5213 tri repeatable_ = tri::either;
5214 tri exclusive_ = tri::either;
5215 tri hasDoc_ = tri::yes;
5216 };
5217
5218
5219
5220
5221
5222
5223 /*************************************************************************//**
5224 *
5225 * @brief documentation formatting options
5226 *
5227 *****************************************************************************/
5228 class doc_formatting
5229 {
5230 public:
5231 using string = doc_string;
5232
5233 /** @brief same as 'first_column' */
5234 #if __cplusplus >= 201402L
5235 [[deprecated]]
5236 #endif
start_column(int col)5237 doc_formatting& start_column(int col) { return first_column(col); }
5238 #if __cplusplus >= 201402L
5239 [[deprecated]]
5240 #endif
start_column() const5241 int start_column() const noexcept { return first_column(); }
5242
5243 /** @brief determines column where documentation printing starts */
5244 doc_formatting&
first_column(int col)5245 first_column(int col) {
5246 //limit to [0,last_column] but push doc_column to the right if neccessary
5247 if(col < 0) col = 0;
5248 else if(col > last_column()) col = last_column();
5249 if(col > doc_column()) doc_column(first_column());
5250 firstCol_ = col;
5251 return *this;
5252 }
first_column() const5253 int first_column() const noexcept {
5254 return firstCol_;
5255 }
5256
5257 /** @brief determines column where docstrings start */
5258 doc_formatting&
doc_column(int col)5259 doc_column(int col) {
5260 //limit to [first_column,last_column]
5261 if(col < 0) col = 0;
5262 else if(col < first_column()) col = first_column();
5263 else if(col > last_column()) col = last_column();
5264 docCol_ = col;
5265 return *this;
5266 }
doc_column() const5267 int doc_column() const noexcept {
5268 return docCol_;
5269 }
5270
5271 /** @brief determines column that no documentation text must exceed;
5272 * (text should be wrapped appropriately after this column)
5273 */
5274 doc_formatting&
last_column(int col)5275 last_column(int col) {
5276 //limit to [first_column,oo] but push doc_column to the left if neccessary
5277 if(col < first_column()) col = first_column();
5278 if(col < doc_column()) doc_column(col);
5279 lastCol_ = col;
5280 return *this;
5281 }
5282
last_column() const5283 int last_column() const noexcept {
5284 return lastCol_;
5285 }
5286
5287 /** @brief determines indent of documentation lines
5288 * for children of a documented group */
indent_size(int indent)5289 doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; }
indent_size() const5290 int indent_size() const noexcept { return indentSize_; }
5291
5292 /** @brief determines string to be used
5293 * if a parameter has no flags and no label */
empty_label(const string & label)5294 doc_formatting& empty_label(const string& label) {
5295 emptyLabel_ = label;
5296 return *this;
5297 }
empty_label() const5298 const string& empty_label() const noexcept { return emptyLabel_; }
5299
5300 /** @brief determines string for separating parameters */
param_separator(const string & sep)5301 doc_formatting& param_separator(const string& sep) {
5302 paramSep_ = sep;
5303 return *this;
5304 }
param_separator() const5305 const string& param_separator() const noexcept { return paramSep_; }
5306
5307 /** @brief determines string for separating groups (in usage lines) */
group_separator(const string & sep)5308 doc_formatting& group_separator(const string& sep) {
5309 groupSep_ = sep;
5310 return *this;
5311 }
group_separator() const5312 const string& group_separator() const noexcept { return groupSep_; }
5313
5314 /** @brief determines string for separating alternative parameters */
alternative_param_separator(const string & sep)5315 doc_formatting& alternative_param_separator(const string& sep) {
5316 altParamSep_ = sep;
5317 return *this;
5318 }
alternative_param_separator() const5319 const string& alternative_param_separator() const noexcept { return altParamSep_; }
5320
5321 /** @brief determines string for separating alternative groups */
alternative_group_separator(const string & sep)5322 doc_formatting& alternative_group_separator(const string& sep) {
5323 altGroupSep_ = sep;
5324 return *this;
5325 }
alternative_group_separator() const5326 const string& alternative_group_separator() const noexcept { return altGroupSep_; }
5327
5328 /** @brief determines string for separating flags of the same parameter */
flag_separator(const string & sep)5329 doc_formatting& flag_separator(const string& sep) {
5330 flagSep_ = sep;
5331 return *this;
5332 }
flag_separator() const5333 const string& flag_separator() const noexcept { return flagSep_; }
5334
5335 /** @brief determnines strings surrounding parameter labels */
5336 doc_formatting&
surround_labels(const string & prefix,const string & postfix)5337 surround_labels(const string& prefix, const string& postfix) {
5338 labelPre_ = prefix;
5339 labelPst_ = postfix;
5340 return *this;
5341 }
label_prefix() const5342 const string& label_prefix() const noexcept { return labelPre_; }
label_postfix() const5343 const string& label_postfix() const noexcept { return labelPst_; }
5344
5345 /** @brief determnines strings surrounding optional parameters/groups */
5346 doc_formatting&
surround_optional(const string & prefix,const string & postfix)5347 surround_optional(const string& prefix, const string& postfix) {
5348 optionPre_ = prefix;
5349 optionPst_ = postfix;
5350 return *this;
5351 }
optional_prefix() const5352 const string& optional_prefix() const noexcept { return optionPre_; }
optional_postfix() const5353 const string& optional_postfix() const noexcept { return optionPst_; }
5354
5355 /** @brief determnines strings surrounding repeatable parameters/groups */
5356 doc_formatting&
surround_repeat(const string & prefix,const string & postfix)5357 surround_repeat(const string& prefix, const string& postfix) {
5358 repeatPre_ = prefix;
5359 repeatPst_ = postfix;
5360 return *this;
5361 }
repeat_prefix() const5362 const string& repeat_prefix() const noexcept { return repeatPre_; }
repeat_postfix() const5363 const string& repeat_postfix() const noexcept { return repeatPst_; }
5364
5365 /** @brief determnines strings surrounding exclusive groups */
5366 doc_formatting&
surround_alternatives(const string & prefix,const string & postfix)5367 surround_alternatives(const string& prefix, const string& postfix) {
5368 alternPre_ = prefix;
5369 alternPst_ = postfix;
5370 return *this;
5371 }
alternatives_prefix() const5372 const string& alternatives_prefix() const noexcept { return alternPre_; }
alternatives_postfix() const5373 const string& alternatives_postfix() const noexcept { return alternPst_; }
5374
5375 /** @brief determnines strings surrounding alternative flags */
5376 doc_formatting&
surround_alternative_flags(const string & prefix,const string & postfix)5377 surround_alternative_flags(const string& prefix, const string& postfix) {
5378 alternFlagPre_ = prefix;
5379 alternFlagPst_ = postfix;
5380 return *this;
5381 }
alternative_flags_prefix() const5382 const string& alternative_flags_prefix() const noexcept { return alternFlagPre_; }
alternative_flags_postfix() const5383 const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; }
5384
5385 /** @brief determnines strings surrounding non-exclusive groups */
5386 doc_formatting&
surround_group(const string & prefix,const string & postfix)5387 surround_group(const string& prefix, const string& postfix) {
5388 groupPre_ = prefix;
5389 groupPst_ = postfix;
5390 return *this;
5391 }
group_prefix() const5392 const string& group_prefix() const noexcept { return groupPre_; }
group_postfix() const5393 const string& group_postfix() const noexcept { return groupPst_; }
5394
5395 /** @brief determnines strings surrounding joinable groups */
5396 doc_formatting&
surround_joinable(const string & prefix,const string & postfix)5397 surround_joinable(const string& prefix, const string& postfix) {
5398 joinablePre_ = prefix;
5399 joinablePst_ = postfix;
5400 return *this;
5401 }
joinable_prefix() const5402 const string& joinable_prefix() const noexcept { return joinablePre_; }
joinable_postfix() const5403 const string& joinable_postfix() const noexcept { return joinablePst_; }
5404
5405 /** @brief determines maximum number of flags per parameter to be printed
5406 * in detailed parameter documentation lines */
max_flags_per_param_in_doc(int max)5407 doc_formatting& max_flags_per_param_in_doc(int max) {
5408 maxAltInDocs_ = max > 0 ? max : 0;
5409 return *this;
5410 }
max_flags_per_param_in_doc() const5411 int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; }
5412
5413 /** @brief determines maximum number of flags per parameter to be printed
5414 * in usage lines */
max_flags_per_param_in_usage(int max)5415 doc_formatting& max_flags_per_param_in_usage(int max) {
5416 maxAltInUsage_ = max > 0 ? max : 0;
5417 return *this;
5418 }
max_flags_per_param_in_usage() const5419 int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; }
5420
5421 /** @brief determines number of empty rows after one single-line
5422 * documentation entry */
line_spacing(int lines)5423 doc_formatting& line_spacing(int lines) {
5424 lineSpc_ = lines > 0 ? lines : 0;
5425 return *this;
5426 }
line_spacing() const5427 int line_spacing() const noexcept { return lineSpc_; }
5428
5429 /** @brief determines number of empty rows before and after a paragraph;
5430 * a paragraph is defined by a documented group or if
5431 * a parameter documentation entry used more than one line */
paragraph_spacing(int lines)5432 doc_formatting& paragraph_spacing(int lines) {
5433 paragraphSpc_ = lines > 0 ? lines : 0;
5434 return *this;
5435 }
paragraph_spacing() const5436 int paragraph_spacing() const noexcept { return paragraphSpc_; }
5437
5438 /** @brief determines if alternative flags with a common prefix should
5439 * be printed in a merged fashion */
merge_alternative_flags_with_common_prefix(bool yes=true)5440 doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) {
5441 mergeAltCommonPfx_ = yes;
5442 return *this;
5443 }
merge_alternative_flags_with_common_prefix() const5444 bool merge_alternative_flags_with_common_prefix() const noexcept {
5445 return mergeAltCommonPfx_;
5446 }
5447
5448 /** @brief determines if joinable flags with a common prefix should
5449 * be printed in a merged fashion */
merge_joinable_with_common_prefix(bool yes=true)5450 doc_formatting& merge_joinable_with_common_prefix(bool yes = true) {
5451 mergeJoinableCommonPfx_ = yes;
5452 return *this;
5453 }
merge_joinable_with_common_prefix() const5454 bool merge_joinable_with_common_prefix() const noexcept {
5455 return mergeJoinableCommonPfx_;
5456 }
5457
5458 /** @brief determines if children of exclusive groups should be printed
5459 * on individual lines if the exceed 'alternatives_min_split_size'
5460 */
split_alternatives(bool yes=true)5461 doc_formatting& split_alternatives(bool yes = true) {
5462 splitTopAlt_ = yes;
5463 return *this;
5464 }
split_alternatives() const5465 bool split_alternatives() const noexcept {
5466 return splitTopAlt_;
5467 }
5468
5469 /** @brief determines how many children exclusive groups can have before
5470 * their children are printed on individual usage lines */
alternatives_min_split_size(int size)5471 doc_formatting& alternatives_min_split_size(int size) {
5472 groupSplitSize_ = size > 0 ? size : 0;
5473 return *this;
5474 }
alternatives_min_split_size() const5475 int alternatives_min_split_size() const noexcept { return groupSplitSize_; }
5476
5477 /** @brief determines whether to ignore new line characters in docstrings
5478 */
ignore_newline_chars(bool yes=true)5479 doc_formatting& ignore_newline_chars(bool yes = true) {
5480 ignoreNewlines_ = yes;
5481 return *this;
5482 }
ignore_newline_chars() const5483 bool ignore_newline_chars() const noexcept {
5484 return ignoreNewlines_;
5485 }
5486
5487 private:
5488 string paramSep_ = string(" ");
5489 string groupSep_ = string(" ");
5490 string altParamSep_ = string("|");
5491 string altGroupSep_ = string(" | ");
5492 string flagSep_ = string(", ");
5493 string labelPre_ = string("<");
5494 string labelPst_ = string(">");
5495 string optionPre_ = string("[");
5496 string optionPst_ = string("]");
5497 string repeatPre_ = string("");
5498 string repeatPst_ = string("...");
5499 string groupPre_ = string("(");
5500 string groupPst_ = string(")");
5501 string alternPre_ = string("(");
5502 string alternPst_ = string(")");
5503 string alternFlagPre_ = string("");
5504 string alternFlagPst_ = string("");
5505 string joinablePre_ = string("(");
5506 string joinablePst_ = string(")");
5507 string emptyLabel_ = string("");
5508 int firstCol_ = 8;
5509 int docCol_ = 20;
5510 int lastCol_ = 100;
5511 int indentSize_ = 4;
5512 int maxAltInUsage_ = 1;
5513 int maxAltInDocs_ = 32;
5514 int lineSpc_ = 0;
5515 int paragraphSpc_ = 1;
5516 int groupSplitSize_ = 3;
5517 bool splitTopAlt_ = true;
5518 bool mergeAltCommonPfx_ = false;
5519 bool mergeJoinableCommonPfx_ = true;
5520 bool ignoreNewlines_ = false;
5521 };
5522
5523
5524
5525 namespace detail {
5526
5527 /*************************************************************************//**
5528 *
5529 * @brief stream wrapper that applies formatting like line wrapping
5530 * to stream data
5531 *
5532 *****************************************************************************/
5533 template<class OStream = std::ostream, class StringT = doc_string>
5534 class formatting_ostream
5535 {
5536 public:
5537 using string_type = StringT;
5538 using size_type = typename string_type::size_type;
5539 using char_type = typename string_type::value_type;
5540
formatting_ostream(OStream & os)5541 formatting_ostream(OStream& os):
5542 os_(os),
5543 curCol_{0}, firstCol_{0}, lastCol_{100},
5544 hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
5545 curBlankLines_{0}, curParagraphLines_{1},
5546 totalNonBlankLines_{0},
5547 ignoreInputNls_{false}
5548 {}
5549
5550
5551 //---------------------------------------------------------------
base() const5552 const OStream& base() const noexcept { return os_; }
base()5553 OStream& base() noexcept { return os_; }
5554
good() const5555 bool good() const { return os_.good(); }
5556
5557
5558 //---------------------------------------------------------------
5559 /** @brief determines the leftmost border of the text body */
first_column(int c)5560 formatting_ostream& first_column(int c) {
5561 firstCol_ = c < 0 ? 0 : c;
5562 return *this;
5563 }
first_column() const5564 int first_column() const noexcept { return firstCol_; }
5565
5566 /** @brief determines the rightmost border of the text body */
last_column(int c)5567 formatting_ostream& last_column(int c) {
5568 lastCol_ = c < 0 ? 0 : c;
5569 return *this;
5570 }
5571
last_column() const5572 int last_column() const noexcept { return lastCol_; }
5573
text_width() const5574 int text_width() const noexcept {
5575 return lastCol_ - firstCol_;
5576 }
5577
5578 /** @brief additional indentation for the 2nd, 3rd, ... line of
5579 a paragraph (sequence of soft-wrapped lines) */
hanging_indent(int amount)5580 formatting_ostream& hanging_indent(int amount) {
5581 hangingIndent_ = amount;
5582 return *this;
5583 }
hanging_indent() const5584 int hanging_indent() const noexcept {
5585 return hangingIndent_;
5586 }
5587
5588 /** @brief amount of blank lines between paragraphs */
paragraph_spacing(int lines)5589 formatting_ostream& paragraph_spacing(int lines) {
5590 paragraphSpacing_ = lines;
5591 return *this;
5592 }
paragraph_spacing() const5593 int paragraph_spacing() const noexcept {
5594 return paragraphSpacing_;
5595 }
5596
5597 /** @brief insert paragraph spacing
5598 if paragraph is at least 'lines' lines long */
min_paragraph_lines_for_spacing(int lines)5599 formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
5600 paragraphSpacingThreshold_ = lines;
5601 return *this;
5602 }
min_paragraph_lines_for_spacing() const5603 int min_paragraph_lines_for_spacing() const noexcept {
5604 return paragraphSpacingThreshold_;
5605 }
5606
5607 /** @brief if set to true, newline characters will be ignored */
ignore_newline_chars(bool yes)5608 formatting_ostream& ignore_newline_chars(bool yes) {
5609 ignoreInputNls_ = yes;
5610 return *this;
5611 }
5612
ignore_newline_chars() const5613 bool ignore_newline_chars() const noexcept {
5614 return ignoreInputNls_;
5615 }
5616
5617
5618 //---------------------------------------------------------------
5619 /* @brief insert 'n' spaces */
write_spaces(int n)5620 void write_spaces(int n) {
5621 if(n < 1) return;
5622 os_ << string_type(size_type(n), ' ');
5623 curCol_ += n;
5624 }
5625
5626 /* @brief go to new line, but continue current paragraph */
wrap_soft(int times=1)5627 void wrap_soft(int times = 1) {
5628 if(times < 1) return;
5629 if(times > 1) {
5630 os_ << string_type(size_type(times), '\n');
5631 } else {
5632 os_ << '\n';
5633 }
5634 curCol_ = 0;
5635 ++curParagraphLines_;
5636 }
5637
5638 /* @brief go to new line, and start a new paragraph */
wrap_hard(int times=1)5639 void wrap_hard(int times = 1) {
5640 if(times < 1) return;
5641
5642 if(paragraph_spacing() > 0 &&
5643 paragraph_lines() >= min_paragraph_lines_for_spacing())
5644 {
5645 times = paragraph_spacing() + 1;
5646 }
5647
5648 if(times > 1) {
5649 os_ << string_type(size_type(times), '\n');
5650 curBlankLines_ += times - 1;
5651 } else {
5652 os_ << '\n';
5653 }
5654 if(at_begin_of_line()) {
5655 ++curBlankLines_;
5656 }
5657 curCol_ = 0;
5658 curParagraphLines_ = 1;
5659 }
5660
5661
5662 //---------------------------------------------------------------
at_begin_of_line() const5663 bool at_begin_of_line() const noexcept {
5664 return curCol_ <= current_line_begin();
5665 }
current_line_begin() const5666 int current_line_begin() const noexcept {
5667 return in_hanging_part_of_paragraph()
5668 ? firstCol_ + hangingIndent_
5669 : firstCol_;
5670 }
5671
current_column() const5672 int current_column() const noexcept {
5673 return curCol_;
5674 }
5675
total_non_blank_lines() const5676 int total_non_blank_lines() const noexcept {
5677 return totalNonBlankLines_;
5678 }
paragraph_lines() const5679 int paragraph_lines() const noexcept {
5680 return curParagraphLines_;
5681 }
blank_lines_before_paragraph() const5682 int blank_lines_before_paragraph() const noexcept {
5683 return curBlankLines_;
5684 }
5685
5686
5687 //---------------------------------------------------------------
5688 template<class T>
5689 friend formatting_ostream&
operator <<(formatting_ostream & os,const T & x)5690 operator << (formatting_ostream& os, const T& x) {
5691 os.write(x);
5692 return os;
5693 }
5694
flush()5695 void flush() {
5696 os_.flush();
5697 }
5698
5699
5700 private:
in_hanging_part_of_paragraph() const5701 bool in_hanging_part_of_paragraph() const noexcept {
5702 return hanging_indent() > 0 && paragraph_lines() > 1;
5703 }
current_line_empty() const5704 bool current_line_empty() const noexcept {
5705 return curCol_ < 1;
5706 }
left_of_text_area() const5707 bool left_of_text_area() const noexcept {
5708 return curCol_ < current_line_begin();
5709 }
right_of_text_area() const5710 bool right_of_text_area() const noexcept {
5711 return curCol_ > lastCol_;
5712 }
columns_left_in_line() const5713 int columns_left_in_line() const noexcept {
5714 return lastCol_ - std::max(current_line_begin(), curCol_);
5715 }
5716
fix_indent()5717 void fix_indent() {
5718 if(left_of_text_area()) {
5719 const auto fst = current_line_begin();
5720 write_spaces(fst - curCol_);
5721 curCol_ = fst;
5722 }
5723 }
5724
5725 template<class Iter>
only_whitespace(Iter first,Iter last) const5726 bool only_whitespace(Iter first, Iter last) const {
5727 return last == std::find_if_not(first, last,
5728 [](char_type c) { return std::isspace(c); });
5729 }
5730
5731 /** @brief write any object */
5732 template<class T>
write(const T & x)5733 void write(const T& x) {
5734 std::ostringstream ss;
5735 ss << x;
5736 write(std::move(ss).str());
5737 }
5738
5739 /** @brief write a stringstream */
write(const std::ostringstream & s)5740 void write(const std::ostringstream& s) {
5741 write(s.str());
5742 }
5743
5744 /** @brief write a string */
write(const string_type & s)5745 void write(const string_type& s) {
5746 write(s.begin(), s.end());
5747 }
5748
5749 /** @brief partition output into lines */
5750 template<class Iter>
write(Iter first,Iter last)5751 void write(Iter first, Iter last)
5752 {
5753 if(first == last) return;
5754 if(*first == '\n') {
5755 if(!ignore_newline_chars()) wrap_hard();
5756 ++first;
5757 if(first == last) return;
5758 }
5759 auto i = std::find(first, last, '\n');
5760 if(i != last) {
5761 if(ignore_newline_chars()) ++i;
5762 if(i != last) {
5763 write_line(first, i);
5764 write(i, last);
5765 }
5766 }
5767 else {
5768 write_line(first, last);
5769 }
5770 }
5771
5772 /** @brief handle line wrapping due to column constraints */
5773 template<class Iter>
write_line(Iter first,Iter last)5774 void write_line(Iter first, Iter last)
5775 {
5776 if(first == last) return;
5777 if(only_whitespace(first, last)) return;
5778
5779 if(right_of_text_area()) wrap_soft();
5780
5781 if(at_begin_of_line()) {
5782 //discard whitespace, it we start a new line
5783 first = std::find_if(first, last,
5784 [](char_type c) { return !std::isspace(c); });
5785 if(first == last) return;
5786 }
5787
5788 const auto n = int(std::distance(first,last));
5789 const auto m = columns_left_in_line();
5790 //if text to be printed is too long for one line -> wrap
5791 if(n > m) {
5792 //break before word, if break is mid-word
5793 auto breakat = first + m;
5794 while(breakat > first && !std::isspace(*breakat)) --breakat;
5795 //could not find whitespace before word -> try after the word
5796 if(!std::isspace(*breakat) && breakat == first) {
5797 breakat = std::find_if(first+m, last,
5798 [](char_type c) { return std::isspace(c); });
5799 }
5800 if(breakat > first) {
5801 if(curCol_ < 1) ++totalNonBlankLines_;
5802 fix_indent();
5803 std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
5804 curBlankLines_ = 0;
5805 }
5806 if(breakat < last) {
5807 wrap_soft();
5808 write_line(breakat, last);
5809 }
5810 }
5811 else {
5812 if(curCol_ < 1) ++totalNonBlankLines_;
5813 fix_indent();
5814 std::copy(first, last, std::ostream_iterator<char_type>(os_));
5815 curCol_ += n;
5816 curBlankLines_ = 0;
5817 }
5818 }
5819
5820 /** @brief write a single character */
write(char_type c)5821 void write(char_type c)
5822 {
5823 if(c == '\n') {
5824 if(!ignore_newline_chars()) wrap_hard();
5825 }
5826 else {
5827 if(at_begin_of_line()) ++totalNonBlankLines_;
5828 fix_indent();
5829 os_ << c;
5830 ++curCol_;
5831 }
5832 }
5833
5834 OStream& os_;
5835 int curCol_;
5836 int firstCol_;
5837 int lastCol_;
5838 int hangingIndent_;
5839 int paragraphSpacing_;
5840 int paragraphSpacingThreshold_;
5841 int curBlankLines_;
5842 int curParagraphLines_;
5843 int totalNonBlankLines_;
5844 bool ignoreInputNls_;
5845 };
5846
5847
5848 }
5849
5850
5851
5852
5853 /*************************************************************************//**
5854 *
5855 * @brief generates usage lines
5856 *
5857 * @details lazily evaluated
5858 *
5859 *****************************************************************************/
5860 class usage_lines
5861 {
5862 public:
5863 using string = doc_string;
5864
usage_lines(const group & cli,string prefix="",const doc_formatting & fmt=doc_formatting{})5865 usage_lines(const group& cli, string prefix = "",
5866 const doc_formatting& fmt = doc_formatting{})
5867 :
5868 cli_(cli), fmt_(fmt), prefix_(std::move(prefix))
5869 {
5870 if(!prefix_.empty()) prefix_ += ' ';
5871 }
5872
usage_lines(const group & cli,const doc_formatting & fmt)5873 usage_lines(const group& cli, const doc_formatting& fmt):
5874 usage_lines(cli, "", fmt)
5875 {}
5876
ommit_outermost_group_surrounders(bool yes)5877 usage_lines& ommit_outermost_group_surrounders(bool yes) {
5878 ommitOutermostSurrounders_ = yes;
5879 return *this;
5880 }
ommit_outermost_group_surrounders() const5881 bool ommit_outermost_group_surrounders() const {
5882 return ommitOutermostSurrounders_;
5883 }
5884
5885 template<class OStream>
operator <<(OStream & os,const usage_lines & p)5886 inline friend OStream& operator << (OStream& os, const usage_lines& p) {
5887 p.write(os);
5888 return os;
5889 }
5890
str() const5891 string str() const {
5892 std::ostringstream os; os << *this; return os.str();
5893 }
5894
5895
5896 private:
5897 using stream_t = detail::formatting_ostream<>;
5898 const group& cli_;
5899 doc_formatting fmt_;
5900 string prefix_;
5901 bool ommitOutermostSurrounders_ = false;
5902
5903
5904 //-----------------------------------------------------
5905 struct context {
5906 group::depth_first_traverser pos;
5907 std::stack<string> separators;
5908 std::stack<string> postfixes;
5909 int level = 0;
5910 const group* outermost = nullptr;
5911 bool linestart = false;
5912 bool useOutermost = true;
5913 int line = 0;
5914
is_singletonclipp::usage_lines::context5915 bool is_singleton() const noexcept {
5916 return linestart && pos.is_last_in_path();
5917 }
is_alternativeclipp::usage_lines::context5918 bool is_alternative() const noexcept {
5919 return pos.parent().exclusive();
5920 }
5921 };
5922
5923
5924 /***************************************************************//**
5925 *
5926 * @brief writes usage text for command line parameters
5927 *
5928 *******************************************************************/
5929 template<class OStream>
write(OStream & os) const5930 void write(OStream& os) const
5931 {
5932 detail::formatting_ostream<OStream> fos(os);
5933 fos.first_column(fmt_.first_column());
5934 fos.last_column(fmt_.last_column());
5935
5936 auto hindent = int(prefix_.size());
5937 if(fos.first_column() + hindent >= int(0.4 * fos.text_width())) {
5938 hindent = fmt_.indent_size();
5939 }
5940 fos.hanging_indent(hindent);
5941
5942 fos.paragraph_spacing(fmt_.paragraph_spacing());
5943 fos.min_paragraph_lines_for_spacing(2);
5944 fos.ignore_newline_chars(fmt_.ignore_newline_chars());
5945
5946 context cur;
5947 cur.pos = cli_.begin_dfs();
5948 cur.linestart = true;
5949 cur.level = cur.pos.level();
5950 cur.outermost = &cli_;
5951
5952 write(fos, cur, prefix_);
5953 }
5954
5955
5956 /***************************************************************//**
5957 *
5958 * @brief writes usage text for command line parameters
5959 *
5960 * @param prefix all that goes in front of current things to print
5961 *
5962 *******************************************************************/
5963 template<class OStream>
write(OStream & os,context cur,string prefix) const5964 void write(OStream& os, context cur, string prefix) const
5965 {
5966 if(!cur.pos) return;
5967
5968 std::ostringstream buf;
5969 if(cur.linestart) buf << prefix;
5970 const auto initPos = buf.tellp();
5971
5972 cur.level = cur.pos.level();
5973
5974 if(cur.useOutermost) {
5975 //we cannot start outside of the outermost group
5976 //so we have to treat it separately
5977 start_group(buf, cur.pos.parent(), cur);
5978 if(!cur.pos) {
5979 os << buf.str();
5980 return;
5981 }
5982 }
5983 else {
5984 //don't visit siblings of starter node
5985 cur.pos.skip_siblings();
5986 }
5987 check_end_group(buf, cur);
5988
5989 do {
5990 if(buf.tellp() > initPos) cur.linestart = false;
5991 if(!cur.linestart && !cur.pos.is_first_in_group()) {
5992 buf << cur.separators.top();
5993 }
5994 if(cur.pos->is_group()) {
5995 start_group(buf, cur.pos->as_group(), cur);
5996 if(!cur.pos) {
5997 os << buf.str();
5998 return;
5999 }
6000 }
6001 else {
6002 buf << param_label(cur.pos->as_param(), cur);
6003 ++cur.pos;
6004 }
6005 check_end_group(buf, cur);
6006 } while(cur.pos);
6007
6008 os << buf.str();
6009 }
6010
6011
6012 /***************************************************************//**
6013 *
6014 * @brief handles pattern group surrounders and separators
6015 * and alternative splitting
6016 *
6017 *******************************************************************/
start_group(std::ostringstream & os,const group & group,context & cur) const6018 void start_group(std::ostringstream& os,
6019 const group& group, context& cur) const
6020 {
6021 //does cur.pos already point to a member or to group itself?
6022 //needed for special treatment of outermost group
6023 const bool alreadyInside = &(cur.pos.parent()) == &group;
6024
6025 auto lbl = joined_label(group, cur);
6026 if(!lbl.empty()) {
6027 os << lbl;
6028 cur.linestart = false;
6029 //skip over entire group as its label has already been created
6030 if(alreadyInside) {
6031 cur.pos.next_after_siblings();
6032 } else {
6033 cur.pos.next_sibling();
6034 }
6035 }
6036 else {
6037 const bool splitAlternatives = group.exclusive() &&
6038 fmt_.split_alternatives() &&
6039 std::any_of(group.begin(), group.end(),
6040 [this](const pattern& p) {
6041 return int(p.param_count()) >= fmt_.alternatives_min_split_size();
6042 });
6043
6044 if(splitAlternatives) {
6045 cur.postfixes.push("");
6046 cur.separators.push("");
6047 //recursively print alternative paths in decision-DAG
6048 //enter group?
6049 if(!alreadyInside) ++cur.pos;
6050 cur.linestart = true;
6051 cur.useOutermost = false;
6052 auto pfx = os.str();
6053 os.str("");
6054 //print paths in DAG starting at each group member
6055 for(std::size_t i = 0; i < group.size(); ++i) {
6056 std::stringstream buf;
6057 cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr;
6058 write(buf, cur, pfx);
6059 if(buf.tellp() > int(pfx.size())) {
6060 os << buf.str();
6061 if(i < group.size()-1) {
6062 if(cur.line > 0) {
6063 os << string(fmt_.line_spacing(), '\n');
6064 }
6065 ++cur.line;
6066 os << '\n';
6067 }
6068 }
6069 cur.pos.next_sibling(); //do not descend into memebers
6070 }
6071 cur.pos.invalidate(); //signal end-of-path
6072 return;
6073 }
6074 else {
6075 //pre & postfixes, separators
6076 auto surround = group_surrounders(group, cur);
6077 os << surround.first;
6078 cur.postfixes.push(std::move(surround.second));
6079 cur.separators.push(group_separator(group, fmt_));
6080 //descend into group?
6081 if(!alreadyInside) ++cur.pos;
6082 }
6083 }
6084 cur.level = cur.pos.level();
6085 }
6086
6087
6088 /***************************************************************//**
6089 *
6090 *******************************************************************/
check_end_group(std::ostringstream & os,context & cur) const6091 void check_end_group(std::ostringstream& os, context& cur) const
6092 {
6093 for(; cur.level > cur.pos.level(); --cur.level) {
6094 os << cur.postfixes.top();
6095 cur.postfixes.pop();
6096 cur.separators.pop();
6097 }
6098 cur.level = cur.pos.level();
6099 }
6100
6101
6102 /***************************************************************//**
6103 *
6104 * @brief makes usage label for one command line parameter
6105 *
6106 *******************************************************************/
param_label(const parameter & p,const context & cur) const6107 string param_label(const parameter& p, const context& cur) const
6108 {
6109 const auto& parent = cur.pos.parent();
6110
6111 const bool startsOptionalSequence =
6112 parent.size() > 1 && p.blocking() && cur.pos.is_first_in_group();
6113
6114 const bool outermost =
6115 ommitOutermostSurrounders_ && cur.outermost == &parent;
6116
6117 const bool showopt = !cur.is_alternative() && !p.required()
6118 && !startsOptionalSequence && !outermost;
6119
6120 const bool showrep = p.repeatable() && !outermost;
6121
6122 string lbl;
6123
6124 if(showrep) lbl += fmt_.repeat_prefix();
6125 if(showopt) lbl += fmt_.optional_prefix();
6126
6127 const auto& flags = p.flags();
6128 if(!flags.empty()) {
6129 const int n = std::min(fmt_.max_flags_per_param_in_usage(),
6130 int(flags.size()));
6131
6132 const bool surrAlt = n > 1 && !showopt && !cur.is_singleton();
6133
6134 if(surrAlt) lbl += fmt_.alternative_flags_prefix();
6135 bool sep = false;
6136 for(int i = 0; i < n; ++i) {
6137 if(sep) {
6138 if(cur.is_singleton())
6139 lbl += fmt_.alternative_group_separator();
6140 else
6141 lbl += fmt_.flag_separator();
6142 }
6143 lbl += flags[i];
6144 sep = true;
6145 }
6146 if(surrAlt) lbl += fmt_.alternative_flags_postfix();
6147 }
6148 else {
6149 if(!p.label().empty()) {
6150 lbl += fmt_.label_prefix()
6151 + p.label()
6152 + fmt_.label_postfix();
6153 } else if(!fmt_.empty_label().empty()) {
6154 lbl += fmt_.label_prefix()
6155 + fmt_.empty_label()
6156 + fmt_.label_postfix();
6157 } else {
6158 return "";
6159 }
6160 }
6161
6162 if(showopt) lbl += fmt_.optional_postfix();
6163 if(showrep) lbl += fmt_.repeat_postfix();
6164
6165 return lbl;
6166 }
6167
6168
6169 /***************************************************************//**
6170 *
6171 * @brief prints flags in one group in a merged fashion
6172 *
6173 *******************************************************************/
joined_label(const group & g,const context & cur) const6174 string joined_label(const group& g, const context& cur) const
6175 {
6176 if(!fmt_.merge_alternative_flags_with_common_prefix() &&
6177 !fmt_.merge_joinable_with_common_prefix()) return "";
6178
6179 const bool flagsonly = std::all_of(g.begin(), g.end(),
6180 [](const pattern& p){
6181 return p.is_param() && !p.as_param().flags().empty();
6182 });
6183
6184 if(!flagsonly) return "";
6185
6186 const bool showOpt = g.all_optional() &&
6187 !(ommitOutermostSurrounders_ && cur.outermost == &g);
6188
6189 auto pfx = g.common_flag_prefix();
6190 if(pfx.empty()) return "";
6191
6192 const auto n = pfx.size();
6193 if(g.exclusive() &&
6194 fmt_.merge_alternative_flags_with_common_prefix())
6195 {
6196 string lbl;
6197 if(showOpt) lbl += fmt_.optional_prefix();
6198 lbl += pfx + fmt_.alternatives_prefix();
6199 bool first = true;
6200 for(const auto& p : g) {
6201 if(p.is_param()) {
6202 if(first)
6203 first = false;
6204 else
6205 lbl += fmt_.alternative_param_separator();
6206 lbl += p.as_param().flags().front().substr(n);
6207 }
6208 }
6209 lbl += fmt_.alternatives_postfix();
6210 if(showOpt) lbl += fmt_.optional_postfix();
6211 return lbl;
6212 }
6213 //no alternatives, but joinable flags
6214 else if(g.joinable() &&
6215 fmt_.merge_joinable_with_common_prefix())
6216 {
6217 const bool allSingleChar = std::all_of(g.begin(), g.end(),
6218 [&](const pattern& p){
6219 return p.is_param() &&
6220 p.as_param().flags().front().substr(n).size() == 1;
6221 });
6222
6223 if(allSingleChar) {
6224 string lbl;
6225 if(showOpt) lbl += fmt_.optional_prefix();
6226 lbl += pfx;
6227 for(const auto& p : g) {
6228 if(p.is_param())
6229 lbl += p.as_param().flags().front().substr(n);
6230 }
6231 if(showOpt) lbl += fmt_.optional_postfix();
6232 return lbl;
6233 }
6234 }
6235
6236 return "";
6237 }
6238
6239
6240 /***************************************************************//**
6241 *
6242 * @return symbols with which to surround a group
6243 *
6244 *******************************************************************/
6245 std::pair<string,string>
group_surrounders(const group & group,const context & cur) const6246 group_surrounders(const group& group, const context& cur) const
6247 {
6248 string prefix;
6249 string postfix;
6250
6251 const bool isOutermost = &group == cur.outermost;
6252 if(isOutermost && ommitOutermostSurrounders_)
6253 return {string{}, string{}};
6254
6255 if(group.exclusive()) {
6256 if(group.all_optional()) {
6257 prefix = fmt_.optional_prefix();
6258 postfix = fmt_.optional_postfix();
6259 if(group.all_flagless()) {
6260 prefix += fmt_.label_prefix();
6261 postfix = fmt_.label_prefix() + postfix;
6262 }
6263 } else if(group.all_flagless()) {
6264 prefix = fmt_.label_prefix();
6265 postfix = fmt_.label_postfix();
6266 } else if(!cur.is_singleton() || !isOutermost) {
6267 prefix = fmt_.alternatives_prefix();
6268 postfix = fmt_.alternatives_postfix();
6269 }
6270 }
6271 else if(group.size() > 1 &&
6272 group.front().blocking() && !group.front().required())
6273 {
6274 prefix = fmt_.optional_prefix();
6275 postfix = fmt_.optional_postfix();
6276 }
6277 else if(group.size() > 1 && cur.is_alternative() &&
6278 &group != cur.outermost)
6279 {
6280 prefix = fmt_.group_prefix();
6281 postfix = fmt_.group_postfix();
6282 }
6283 else if(!group.exclusive() &&
6284 group.joinable() && !cur.linestart)
6285 {
6286 prefix = fmt_.joinable_prefix();
6287 postfix = fmt_.joinable_postfix();
6288 }
6289
6290 if(group.repeatable()) {
6291 if(prefix.empty()) prefix = fmt_.group_prefix();
6292 prefix = fmt_.repeat_prefix() + prefix;
6293 if(postfix.empty()) postfix = fmt_.group_postfix();
6294 postfix += fmt_.repeat_postfix();
6295 }
6296
6297 return {std::move(prefix), std::move(postfix)};
6298 }
6299
6300
6301 /***************************************************************//**
6302 *
6303 * @return symbol that separates members of a group
6304 *
6305 *******************************************************************/
6306 static string
group_separator(const group & group,const doc_formatting & fmt)6307 group_separator(const group& group, const doc_formatting& fmt)
6308 {
6309 const bool only1ParamPerMember = std::all_of(group.begin(), group.end(),
6310 [](const pattern& p) { return p.param_count() < 2; });
6311
6312 if(only1ParamPerMember) {
6313 if(group.exclusive()) {
6314 return fmt.alternative_param_separator();
6315 } else {
6316 return fmt.param_separator();
6317 }
6318 }
6319 else { //there is at least one large group inside
6320 if(group.exclusive()) {
6321 return fmt.alternative_group_separator();
6322 } else {
6323 return fmt.group_separator();
6324 }
6325 }
6326 }
6327 };
6328
6329
6330
6331
6332 /*************************************************************************//**
6333 *
6334 * @brief generates parameter and group documentation from docstrings
6335 *
6336 * @details lazily evaluated
6337 *
6338 *****************************************************************************/
6339 class documentation
6340 {
6341 public:
6342 using string = doc_string;
6343 using filter_function = std::function<bool(const parameter&)>;
6344
documentation(const group & cli,const doc_formatting & fmt=doc_formatting{},filter_function filter=param_filter{})6345 documentation(const group& cli,
6346 const doc_formatting& fmt = doc_formatting{},
6347 filter_function filter = param_filter{})
6348 :
6349 cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{std::move(filter)}
6350 {
6351 //necessary, because we re-use "usage_lines" to generate
6352 //labels for documented groups
6353 usgFmt_.max_flags_per_param_in_usage(
6354 usgFmt_.max_flags_per_param_in_doc());
6355 }
6356
documentation(const group & cli,filter_function filter)6357 documentation(const group& cli, filter_function filter) :
6358 documentation{cli, doc_formatting{}, std::move(filter)}
6359 {}
6360
documentation(const group & cli,const param_filter & filter)6361 documentation(const group& cli, const param_filter& filter) :
6362 documentation{cli, doc_formatting{},
6363 [filter](const parameter& p) { return filter(p); }}
6364 {}
6365
6366 template<class OStream>
operator <<(OStream & os,const documentation & p)6367 inline friend OStream& operator << (OStream& os, const documentation& p) {
6368 p.write(os);
6369 return os;
6370 }
6371
str() const6372 string str() const {
6373 std::ostringstream os;
6374 write(os);
6375 return os.str();
6376 }
6377
6378
6379 private:
6380 using dfs_traverser = group::depth_first_traverser;
6381
6382 const group& cli_;
6383 doc_formatting fmt_;
6384 doc_formatting usgFmt_;
6385 filter_function filter_;
6386 enum class paragraph { param, group };
6387
6388
6389 /***************************************************************//**
6390 *
6391 * @brief writes documentation to output stream
6392 *
6393 *******************************************************************/
6394 template<class OStream>
write(OStream & os) const6395 void write(OStream& os) const {
6396 detail::formatting_ostream<OStream> fos(os);
6397 fos.first_column(fmt_.first_column());
6398 fos.last_column(fmt_.last_column());
6399 fos.hanging_indent(0);
6400 fos.paragraph_spacing(0);
6401 fos.ignore_newline_chars(fmt_.ignore_newline_chars());
6402 print_doc(fos, cli_);
6403 }
6404
6405
6406 /***************************************************************//**
6407 *
6408 * @brief writes full documentation text for command line parameters
6409 *
6410 *******************************************************************/
6411 template<class OStream>
print_doc(detail::formatting_ostream<OStream> & os,const group & cli,int indentLvl=0) const6412 void print_doc(detail::formatting_ostream<OStream>& os,
6413 const group& cli, int indentLvl = 0) const
6414 {
6415 if(cli.empty()) return;
6416
6417 //if group itself doesn't have docstring
6418 if(cli.doc().empty()) {
6419 for(const auto& p : cli) {
6420 print_doc(os, p, indentLvl);
6421 }
6422 }
6423 else { //group itself does have docstring
6424 bool anyDocInside = std::any_of(cli.begin(), cli.end(),
6425 [](const pattern& p){ return !p.doc().empty(); });
6426
6427 if(anyDocInside) { //group docstring as title, then child entries
6428 handle_spacing(os, paragraph::group, indentLvl);
6429 os << cli.doc();
6430 for(const auto& p : cli) {
6431 print_doc(os, p, indentLvl + 1);
6432 }
6433 }
6434 else { //group label first then group docstring
6435 auto lbl = usage_lines(cli, usgFmt_)
6436 .ommit_outermost_group_surrounders(true).str();
6437
6438 str::trim(lbl);
6439 handle_spacing(os, paragraph::param, indentLvl);
6440 print_entry(os, lbl, cli.doc());
6441 }
6442 }
6443 }
6444
6445
6446 /***************************************************************//**
6447 *
6448 * @brief writes documentation text for one group or parameter
6449 *
6450 *******************************************************************/
6451 template<class OStream>
print_doc(detail::formatting_ostream<OStream> & os,const pattern & ptrn,int indentLvl) const6452 void print_doc(detail::formatting_ostream<OStream>& os,
6453 const pattern& ptrn, int indentLvl) const
6454 {
6455 if(ptrn.is_group()) {
6456 print_doc(os, ptrn.as_group(), indentLvl);
6457 }
6458 else {
6459 const auto& p = ptrn.as_param();
6460 if(!filter_(p)) return;
6461
6462 handle_spacing(os, paragraph::param, indentLvl);
6463 print_entry(os, param_label(p, fmt_), p.doc());
6464 }
6465 }
6466
6467 /***************************************************************//**
6468 *
6469 * @brief handles line and paragraph spacings
6470 *
6471 *******************************************************************/
6472 template<class OStream>
handle_spacing(detail::formatting_ostream<OStream> & os,paragraph p,int indentLvl) const6473 void handle_spacing(detail::formatting_ostream<OStream>& os,
6474 paragraph p, int indentLvl) const
6475 {
6476 const auto oldIndent = os.first_column();
6477 const auto indent = fmt_.first_column() + indentLvl * fmt_.indent_size();
6478
6479 if(os.total_non_blank_lines() < 1) {
6480 os.first_column(indent);
6481 return;
6482 }
6483
6484 if(os.paragraph_lines() > 1 || indent < oldIndent) {
6485 os.wrap_hard(fmt_.paragraph_spacing() + 1);
6486 } else {
6487 os.wrap_hard();
6488 }
6489
6490 if(p == paragraph::group) {
6491 if(os.blank_lines_before_paragraph() < fmt_.paragraph_spacing()) {
6492 os.wrap_hard(fmt_.paragraph_spacing() - os.blank_lines_before_paragraph());
6493 }
6494 }
6495 else if(os.blank_lines_before_paragraph() < fmt_.line_spacing()) {
6496 os.wrap_hard(fmt_.line_spacing() - os.blank_lines_before_paragraph());
6497 }
6498 os.first_column(indent);
6499 }
6500
6501 /*********************************************************************//**
6502 *
6503 * @brief prints one entry = label + docstring
6504 *
6505 ************************************************************************/
6506 template<class OStream>
print_entry(detail::formatting_ostream<OStream> & os,const string & label,const string & docstr) const6507 void print_entry(detail::formatting_ostream<OStream>& os,
6508 const string& label, const string& docstr) const
6509 {
6510 if(label.empty()) return;
6511
6512 os << label;
6513
6514 if(!docstr.empty()) {
6515 if(os.current_column() >= fmt_.doc_column()) os.wrap_soft();
6516 const auto oldcol = os.first_column();
6517 os.first_column(fmt_.doc_column());
6518 os << docstr;
6519 os.first_column(oldcol);
6520 }
6521 }
6522
6523
6524 /*********************************************************************//**
6525 *
6526 * @brief makes label for one parameter
6527 *
6528 ************************************************************************/
6529 static doc_string
param_label(const parameter & param,const doc_formatting & fmt)6530 param_label(const parameter& param, const doc_formatting& fmt)
6531 {
6532 doc_string lbl;
6533
6534 if(param.repeatable()) lbl += fmt.repeat_prefix();
6535
6536 const auto& flags = param.flags();
6537 if(!flags.empty()) {
6538 lbl += flags[0];
6539 const int n = std::min(fmt.max_flags_per_param_in_doc(),
6540 int(flags.size()));
6541 for(int i = 1; i < n; ++i) {
6542 lbl += fmt.flag_separator() + flags[i];
6543 }
6544 }
6545 else if(!param.label().empty() || !fmt.empty_label().empty()) {
6546 lbl += fmt.label_prefix();
6547 if(!param.label().empty()) {
6548 lbl += param.label();
6549 } else {
6550 lbl += fmt.empty_label();
6551 }
6552 lbl += fmt.label_postfix();
6553 }
6554
6555 if(param.repeatable()) lbl += fmt.repeat_postfix();
6556
6557 return lbl;
6558 }
6559
6560 };
6561
6562
6563
6564
6565 /*************************************************************************//**
6566 *
6567 * @brief stores strings for man page sections
6568 *
6569 *****************************************************************************/
6570 class man_page
6571 {
6572 public:
6573 //---------------------------------------------------------------
6574 using string = doc_string;
6575
6576 //---------------------------------------------------------------
6577 /** @brief man page section */
6578 class section {
6579 public:
6580 using string = doc_string;
6581
section(string stitle,string scontent)6582 section(string stitle, string scontent):
6583 title_{std::move(stitle)}, content_{std::move(scontent)}
6584 {}
6585
title() const6586 const string& title() const noexcept { return title_; }
content() const6587 const string& content() const noexcept { return content_; }
6588
6589 private:
6590 string title_;
6591 string content_;
6592 };
6593
6594 private:
6595 using section_store = std::vector<section>;
6596
6597 public:
6598 //---------------------------------------------------------------
6599 using value_type = section;
6600 using const_iterator = section_store::const_iterator;
6601 using size_type = section_store::size_type;
6602
6603
6604 //---------------------------------------------------------------
6605 man_page&
append_section(string title,string content)6606 append_section(string title, string content)
6607 {
6608 sections_.emplace_back(std::move(title), std::move(content));
6609 return *this;
6610 }
6611 //-----------------------------------------------------
6612 man_page&
prepend_section(string title,string content)6613 prepend_section(string title, string content)
6614 {
6615 sections_.emplace(sections_.begin(),
6616 std::move(title), std::move(content));
6617 return *this;
6618 }
6619
6620
6621 //---------------------------------------------------------------
operator [](size_type index) const6622 const section& operator [] (size_type index) const noexcept {
6623 return sections_[index];
6624 }
6625
6626 //---------------------------------------------------------------
size() const6627 size_type size() const noexcept { return sections_.size(); }
6628
empty() const6629 bool empty() const noexcept { return sections_.empty(); }
6630
6631
6632 //---------------------------------------------------------------
begin() const6633 const_iterator begin() const noexcept { return sections_.begin(); }
end() const6634 const_iterator end() const noexcept { return sections_.end(); }
6635
6636
6637 //---------------------------------------------------------------
program_name(const string & n)6638 man_page& program_name(const string& n) {
6639 progName_ = n;
6640 return *this;
6641 }
program_name(string && n)6642 man_page& program_name(string&& n) {
6643 progName_ = std::move(n);
6644 return *this;
6645 }
program_name() const6646 const string& program_name() const noexcept {
6647 return progName_;
6648 }
6649
6650
6651 //---------------------------------------------------------------
section_row_spacing(int rows)6652 man_page& section_row_spacing(int rows) {
6653 sectionSpc_ = rows > 0 ? rows : 0;
6654 return *this;
6655 }
section_row_spacing() const6656 int section_row_spacing() const noexcept { return sectionSpc_; }
6657
6658
6659 private:
6660 int sectionSpc_ = 1;
6661 section_store sections_;
6662 string progName_;
6663 };
6664
6665
6666
6667 /*************************************************************************//**
6668 *
6669 * @brief generates man sections from command line parameters
6670 * with sections "synopsis" and "options"
6671 *
6672 *****************************************************************************/
6673 inline man_page
make_man_page(const group & cli,doc_string progname="",const doc_formatting & fmt=doc_formatting{})6674 make_man_page(const group& cli,
6675 doc_string progname = "",
6676 const doc_formatting& fmt = doc_formatting{})
6677 {
6678 man_page man;
6679 man.append_section("SYNOPSIS", usage_lines(cli,progname,fmt).str());
6680 man.append_section("OPTIONS", documentation(cli,fmt).str());
6681 return man;
6682 }
6683
6684
6685
6686 /*************************************************************************//**
6687 *
6688 * @brief generates man page based on command line parameters
6689 *
6690 *****************************************************************************/
6691 template<class OStream>
6692 OStream&
operator <<(OStream & os,const man_page & man)6693 operator << (OStream& os, const man_page& man)
6694 {
6695 bool first = true;
6696 const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n');
6697 for(const auto& section : man) {
6698 if(!section.content().empty()) {
6699 if(first) first = false; else os << secSpc;
6700 if(!section.title().empty()) os << section.title() << '\n';
6701 os << section.content();
6702 }
6703 }
6704 os << '\n';
6705 return os;
6706 }
6707
6708
6709
6710
6711
6712 /*************************************************************************//**
6713 *
6714 * @brief printing methods for debugging command line interfaces
6715 *
6716 *****************************************************************************/
6717 namespace debug {
6718
6719
6720 /*************************************************************************//**
6721 *
6722 * @brief prints first flag or value label of a parameter
6723 *
6724 *****************************************************************************/
doc_label(const parameter & p)6725 inline doc_string doc_label(const parameter& p)
6726 {
6727 if(!p.flags().empty()) return p.flags().front();
6728 if(!p.label().empty()) return p.label();
6729 return doc_string{"<?>"};
6730 }
6731
doc_label(const group &)6732 inline doc_string doc_label(const group&)
6733 {
6734 return "<group>";
6735 }
6736
doc_label(const pattern & p)6737 inline doc_string doc_label(const pattern& p)
6738 {
6739 return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param());
6740 }
6741
6742
6743 /*************************************************************************//**
6744 *
6745 * @brief prints parsing result
6746 *
6747 *****************************************************************************/
6748 template<class OStream>
print(OStream & os,const parsing_result & result)6749 void print(OStream& os, const parsing_result& result)
6750 {
6751 for(const auto& m : result) {
6752 os << "#" << m.index() << " " << m.arg() << " -> ";
6753 auto p = m.param();
6754 if(p) {
6755 os << doc_label(*p) << " \t";
6756 if(m.repeat() > 0) {
6757 os << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
6758 << m.repeat() << "]";
6759 }
6760 if(m.blocked()) os << " [blocked]";
6761 if(m.conflict()) os << " [conflict]";
6762 os << '\n';
6763 }
6764 else {
6765 os << " [unmapped]\n";
6766 }
6767 }
6768
6769 for(const auto& m : result.missing()) {
6770 auto p = m.param();
6771 if(p) {
6772 os << doc_label(*p) << " \t";
6773 os << " [missing after " << m.after_index() << "]\n";
6774 }
6775 }
6776 }
6777
6778
6779 /*************************************************************************//**
6780 *
6781 * @brief prints parameter label and some properties
6782 *
6783 *****************************************************************************/
6784 template<class OStream>
print(OStream & os,const parameter & p)6785 void print(OStream& os, const parameter& p)
6786 {
6787 if(p.blocking()) os << '!';
6788 if(!p.required()) os << '[';
6789 os << doc_label(p);
6790 if(p.repeatable()) os << "...";
6791 if(!p.required()) os << "]";
6792 }
6793
6794
6795 //-------------------------------------------------------------------
6796 template<class OStream>
6797 void print(OStream& os, const group& g, int level = 0);
6798
6799
6800 /*************************************************************************//**
6801 *
6802 * @brief prints group or parameter; uses indentation
6803 *
6804 *****************************************************************************/
6805 template<class OStream>
print(OStream & os,const pattern & param,int level=0)6806 void print(OStream& os, const pattern& param, int level = 0)
6807 {
6808 if(param.is_group()) {
6809 print(os, param.as_group(), level);
6810 }
6811 else {
6812 os << doc_string(4*level, ' ');
6813 print(os, param.as_param());
6814 }
6815 }
6816
6817
6818 /*************************************************************************//**
6819 *
6820 * @brief prints group and its contents; uses indentation
6821 *
6822 *****************************************************************************/
6823 template<class OStream>
print(OStream & os,const group & g,int level)6824 void print(OStream& os, const group& g, int level)
6825 {
6826 auto indent = doc_string(4*level, ' ');
6827 os << indent;
6828 if(g.blocking()) os << '!';
6829 if(g.joinable()) os << 'J';
6830 os << (g.exclusive() ? "(|\n" : "(\n");
6831 for(const auto& p : g) {
6832 print(os, p, level+1);
6833 }
6834 os << '\n' << indent << (g.exclusive() ? "|)" : ")");
6835 if(g.repeatable()) os << "...";
6836 os << '\n';
6837 }
6838
6839
6840 } // namespace debug
6841 } //namespace clipp
6842
6843 #endif
6844