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