7025 lines
		
	
	
		
			212 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			7025 lines
		
	
	
		
			212 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*****************************************************************************
 | |
|  *  ___  _    _   ___ ___
 | |
|  * |  _|| |  | | | _ \ _ \   CLIPP - command line interfaces for modern C++
 | |
|  * | |_ | |_ | | |  _/  _/   version 1.2.3
 | |
|  * |___||___||_| |_| |_|     https://github.com/muellan/clipp
 | |
|  *
 | |
|  * Licensed under the MIT License <http://opensource.org/licenses/MIT>.
 | |
|  * Copyright (c) 2017-2018 André Müller <foss@andremueller-online.de>
 | |
|  *
 | |
|  * ---------------------------------------------------------------------------
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a
 | |
|  * copy of this software and associated documentation files (the "Software"),
 | |
|  * to deal in the Software without restriction, including without limitation
 | |
|  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 | |
|  * and/or sell copies of the Software, and to permit persons to whom the
 | |
|  * Software is furnished to do so, subject to the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice shall be included in
 | |
|  * all copies or substantial portions of the Software.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 | |
|  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 | |
|  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 | |
|  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 | |
|  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 | |
|  * OTHER DEALINGS IN THE SOFTWARE.
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| 
 | |
| #ifndef AM_CLIPP_H__
 | |
| #define AM_CLIPP_H__
 | |
| 
 | |
| #include <cstring>
 | |
| #include <string>
 | |
| #include <cstdlib>
 | |
| #include <cstring>
 | |
| #include <cctype>
 | |
| #include <memory>
 | |
| #include <vector>
 | |
| #include <limits>
 | |
| #include <stack>
 | |
| #include <algorithm>
 | |
| #include <sstream>
 | |
| #include <utility>
 | |
| #include <iterator>
 | |
| #include <functional>
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief primary namespace
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace clipp {
 | |
| 
 | |
| 
 | |
| 
 | |
| /*****************************************************************************
 | |
|  *
 | |
|  * basic constants and datatype definitions
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| using arg_index = int;
 | |
| 
 | |
| using arg_string = std::string;
 | |
| using doc_string = std::string;
 | |
| 
 | |
| using arg_list  = std::vector<arg_string>;
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief tristate
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| enum class tri : char { no, yes, either };
 | |
| 
 | |
| inline constexpr bool operator == (tri t, bool b) noexcept {
 | |
|     return b ? t != tri::no : t != tri::yes;
 | |
| }
 | |
| inline constexpr bool operator == (bool b, tri t) noexcept { return  (t == b); }
 | |
| inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); }
 | |
| inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief (start,size) index range
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class subrange {
 | |
| public:
 | |
|     using size_type = arg_string::size_type;
 | |
| 
 | |
|     /** @brief default: no match */
 | |
|     explicit constexpr
 | |
|     subrange() noexcept :
 | |
|         at_{arg_string::npos}, length_{0}
 | |
|     {}
 | |
| 
 | |
|     /** @brief match length & position within subject string */
 | |
|     explicit constexpr
 | |
|     subrange(size_type pos, size_type len) noexcept :
 | |
|         at_{pos}, length_{len}
 | |
|     {}
 | |
| 
 | |
|     /** @brief position of the match within the subject string */
 | |
|     constexpr size_type at()     const noexcept { return at_; }
 | |
|     /** @brief length of the matching subsequence */
 | |
|     constexpr size_type length() const noexcept { return length_; }
 | |
| 
 | |
|     /** @brief returns true, if query string is a prefix of the subject string */
 | |
|     constexpr bool prefix() const noexcept {
 | |
|         return at_ == 0;
 | |
|     }
 | |
| 
 | |
|     /** @brief returns true, if query is a substring of the query string */
 | |
|     constexpr explicit operator bool () const noexcept {
 | |
|         return at_ != arg_string::npos;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     size_type at_;
 | |
|     size_type length_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief match predicates
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| using match_predicate = std::function<bool(const arg_string&)>;
 | |
| using match_function  = std::function<subrange(const arg_string&)>;
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!)
 | |
|  *        no interface guarantees; might be changed or removed in the future
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace traits {
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief function (class) signature type trait
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Fn, class Ret, class... Args>
 | |
| constexpr auto
 | |
| check_is_callable(int) -> decltype(
 | |
|     std::declval<Fn>()(std::declval<Args>()...),
 | |
|     std::integral_constant<bool,
 | |
|         std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} );
 | |
| 
 | |
| template<class,class,class...>
 | |
| constexpr auto
 | |
| check_is_callable(long) -> std::false_type;
 | |
| 
 | |
| template<class Fn, class Ret>
 | |
| constexpr auto
 | |
| check_is_callable_without_arg(int) -> decltype(
 | |
|     std::declval<Fn>()(),
 | |
|     std::integral_constant<bool,
 | |
|         std::is_same<Ret,typename std::result_of<Fn()>::type>::value>{} );
 | |
| 
 | |
| template<class,class>
 | |
| constexpr auto
 | |
| check_is_callable_without_arg(long) -> std::false_type;
 | |
| 
 | |
| 
 | |
| 
 | |
| template<class Fn, class... Args>
 | |
| constexpr auto
 | |
| check_is_void_callable(int) -> decltype(
 | |
|     std::declval<Fn>()(std::declval<Args>()...), std::true_type{});
 | |
| 
 | |
| template<class,class,class...>
 | |
| constexpr auto
 | |
| check_is_void_callable(long) -> std::false_type;
 | |
| 
 | |
| template<class Fn>
 | |
| constexpr auto
 | |
| check_is_void_callable_without_arg(int) -> decltype(
 | |
|     std::declval<Fn>()(), std::true_type{});
 | |
| 
 | |
| template<class>
 | |
| constexpr auto
 | |
| check_is_void_callable_without_arg(long) -> std::false_type;
 | |
| 
 | |
| 
 | |
| 
 | |
| template<class Fn, class Ret>
 | |
| struct is_callable;
 | |
| 
 | |
| 
 | |
| template<class Fn, class Ret, class... Args>
 | |
| struct is_callable<Fn, Ret(Args...)> :
 | |
|     decltype(check_is_callable<Fn,Ret,Args...>(0))
 | |
| {};
 | |
| 
 | |
| template<class Fn, class Ret>
 | |
| struct is_callable<Fn,Ret()> :
 | |
|     decltype(check_is_callable_without_arg<Fn,Ret>(0))
 | |
| {};
 | |
| 
 | |
| 
 | |
| template<class Fn, class... Args>
 | |
| struct is_callable<Fn, void(Args...)> :
 | |
|     decltype(check_is_void_callable<Fn,Args...>(0))
 | |
| {};
 | |
| 
 | |
| template<class Fn>
 | |
| struct is_callable<Fn,void()> :
 | |
|     decltype(check_is_void_callable_without_arg<Fn>(0))
 | |
| {};
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief input range type trait
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| constexpr auto
 | |
| check_is_input_range(int) -> decltype(
 | |
|     begin(std::declval<T>()), end(std::declval<T>()),
 | |
|     std::true_type{});
 | |
| 
 | |
| template<class T>
 | |
| constexpr auto
 | |
| check_is_input_range(char) -> decltype(
 | |
|     std::begin(std::declval<T>()), std::end(std::declval<T>()),
 | |
|     std::true_type{});
 | |
| 
 | |
| template<class>
 | |
| constexpr auto
 | |
| check_is_input_range(long) -> std::false_type;
 | |
| 
 | |
| template<class T>
 | |
| struct is_input_range :
 | |
|     decltype(check_is_input_range<T>(0))
 | |
| {};
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief size() member type trait
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| constexpr auto
 | |
| check_has_size_getter(int) ->
 | |
|     decltype(std::declval<T>().size(), std::true_type{});
 | |
| 
 | |
| template<class>
 | |
| constexpr auto
 | |
| check_has_size_getter(long) -> std::false_type;
 | |
| 
 | |
| template<class T>
 | |
| struct has_size_getter :
 | |
|     decltype(check_has_size_getter<T>(0))
 | |
| {};
 | |
| 
 | |
| } // namespace traits
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
 | |
|  *        no interface guarantees; might be changed or removed in the future
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace detail {
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  * @brief forwards string to first non-whitespace char;
 | |
|  *        std string -> unsigned conv yields max value, but we want 0;
 | |
|  *        also checks for nullptr
 | |
|  *****************************************************************************/
 | |
| inline bool
 | |
| fwd_to_unsigned_int(const char*& s)
 | |
| {
 | |
|     if(!s) return false;
 | |
|     for(; std::isspace(*s); ++s);
 | |
|     if(!s[0] || s[0] == '-') return false;
 | |
|     if(s[0] == '-') return false;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief value limits clamping
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T, class V, bool = (sizeof(V) > sizeof(T))>
 | |
| struct limits_clamped {
 | |
|     static T from(const V& v) {
 | |
|         if(v >= V(std::numeric_limits<T>::max())) {
 | |
|             return std::numeric_limits<T>::max();
 | |
|         }
 | |
|         if(v <= V(std::numeric_limits<T>::lowest())) {
 | |
|             return std::numeric_limits<T>::lowest();
 | |
|         }
 | |
|         return T(v);
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<class T, class V>
 | |
| struct limits_clamped<T,V,false> {
 | |
|     static T from(const V& v) { return T(v); }
 | |
| };
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns value of v as a T, clamped at T's maximum
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T, class V>
 | |
| inline T clamped_on_limits(const V& v) {
 | |
|     return limits_clamped<T,V>::from(v);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief type conversion helpers
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| struct make {
 | |
|     static inline T from(const char* s) {
 | |
|         if(!s) return false;
 | |
|         //a conversion from const char* to / must exist
 | |
|         return static_cast<T>(s);
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<bool> {
 | |
|     static inline bool from(const char* s) {
 | |
|         if(!s) return false;
 | |
|         return static_cast<bool>(s);
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<unsigned char> {
 | |
|     static inline unsigned char from(const char* s) {
 | |
|         if(!fwd_to_unsigned_int(s)) return (0);
 | |
|         return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<unsigned short int> {
 | |
|     static inline unsigned short int from(const char* s) {
 | |
|         if(!fwd_to_unsigned_int(s)) return (0);
 | |
|         return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<unsigned int> {
 | |
|     static inline unsigned int from(const char* s) {
 | |
|         if(!fwd_to_unsigned_int(s)) return (0);
 | |
|         return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<unsigned long int> {
 | |
|     static inline unsigned long int from(const char* s) {
 | |
|         if(!fwd_to_unsigned_int(s)) return (0);
 | |
|         return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<unsigned long long int> {
 | |
|     static inline unsigned long long int from(const char* s) {
 | |
|         if(!fwd_to_unsigned_int(s)) return (0);
 | |
|         return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<char> {
 | |
|     static inline char from(const char* s) {
 | |
|         //parse as single character?
 | |
|         const auto n = std::strlen(s);
 | |
|         if(n == 1) return s[0];
 | |
|         //parse as integer
 | |
|         return clamped_on_limits<char>(std::strtoll(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<short int> {
 | |
|     static inline short int from(const char* s) {
 | |
|         return clamped_on_limits<short int>(std::strtoll(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<int> {
 | |
|     static inline int from(const char* s) {
 | |
|         return clamped_on_limits<int>(std::strtoll(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<long int> {
 | |
|     static inline long int from(const char* s) {
 | |
|         return clamped_on_limits<long int>(std::strtoll(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<long long int> {
 | |
|     static inline long long int from(const char* s) {
 | |
|         return (std::strtoll(s,nullptr,10));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<float> {
 | |
|     static inline float from(const char* s) {
 | |
|         return (std::strtof(s,nullptr));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<double> {
 | |
|     static inline double from(const char* s) {
 | |
|         return (std::strtod(s,nullptr));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<long double> {
 | |
|     static inline long double from(const char* s) {
 | |
|         return (std::strtold(s,nullptr));
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct make<std::string> {
 | |
|     static inline std::string from(const char* s) {
 | |
|         return std::string(s);
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief assigns boolean constant to one or multiple target objects
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T, class V = T>
 | |
| class assign_value
 | |
| {
 | |
| public:
 | |
|     template<class X>
 | |
|     explicit constexpr
 | |
|     assign_value(T& target, X&& value) noexcept :
 | |
|         t_{std::addressof(target)}, v_{std::forward<X>(value)}
 | |
|     {}
 | |
| 
 | |
|     void operator () () const {
 | |
|         if(t_) *t_ = v_;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     T* t_;
 | |
|     V v_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief flips bools
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class flip_bool
 | |
| {
 | |
| public:
 | |
|     explicit constexpr
 | |
|     flip_bool(bool& target) noexcept :
 | |
|         b_{&target}
 | |
|     {}
 | |
| 
 | |
|     void operator () () const {
 | |
|         if(b_) *b_ = !*b_;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     bool* b_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief increments using operator ++
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| class increment
 | |
| {
 | |
| public:
 | |
|     explicit constexpr
 | |
|     increment(T& target) noexcept : t_{std::addressof(target)} {}
 | |
| 
 | |
|     void operator () () const {
 | |
|         if(t_) ++(*t_);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     T* t_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief decrements using operator --
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| class decrement
 | |
| {
 | |
| public:
 | |
|     explicit constexpr
 | |
|     decrement(T& target) noexcept : t_{std::addressof(target)} {}
 | |
| 
 | |
|     void operator () () const {
 | |
|         if(t_) --(*t_);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     T* t_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief increments by a fixed amount using operator +=
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| class increment_by
 | |
| {
 | |
| public:
 | |
|     explicit constexpr
 | |
|     increment_by(T& target, T by) noexcept :
 | |
|         t_{std::addressof(target)}, by_{std::move(by)}
 | |
|     {}
 | |
| 
 | |
|     void operator () () const {
 | |
|         if(t_) (*t_) += by_;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     T* t_;
 | |
|     T by_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a value from a string and assigns it to an object
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| class map_arg_to
 | |
| {
 | |
| public:
 | |
|     explicit constexpr
 | |
|     map_arg_to(T& target) noexcept : t_{std::addressof(target)} {}
 | |
| 
 | |
|     void operator () (const char* s) const {
 | |
|         if(t_ && s) *t_ = detail::make<T>::from(s);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     T* t_;
 | |
| };
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| /**
 | |
|  * @brief specialization for vectors: append element
 | |
|  */
 | |
| template<class T>
 | |
| class map_arg_to<std::vector<T>>
 | |
| {
 | |
| public:
 | |
|     map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {}
 | |
| 
 | |
|     void operator () (const char* s) const {
 | |
|         if(t_ && s) t_->push_back(detail::make<T>::from(s));
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     std::vector<T>* t_;
 | |
| };
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| /**
 | |
|  * @brief specialization for bools:
 | |
|  *        set to true regardless of string content
 | |
|  */
 | |
| template<>
 | |
| class map_arg_to<bool>
 | |
| {
 | |
| public:
 | |
|     map_arg_to(bool& target): t_{&target} {}
 | |
| 
 | |
|     void operator () (const char* s) const {
 | |
|         if(t_ && s) *t_ = true;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     bool* t_;
 | |
| };
 | |
| 
 | |
| 
 | |
| } // namespace detail
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief string matching and processing tools
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| 
 | |
| namespace str {
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief converts string to value of target type 'T'
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| T make(const arg_string& s)
 | |
| {
 | |
|     return detail::make<T>::from(s);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief removes trailing whitespace from string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline void
 | |
| trimr(std::basic_string<C,T,A>& s)
 | |
| {
 | |
|     if(s.empty()) return;
 | |
| 
 | |
|     s.erase(
 | |
|         std::find_if_not(s.rbegin(), s.rend(),
 | |
|                          [](char c) { return std::isspace(c);} ).base(),
 | |
|         s.end() );
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief removes leading whitespace from string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline void
 | |
| triml(std::basic_string<C,T,A>& s)
 | |
| {
 | |
|     if(s.empty()) return;
 | |
| 
 | |
|     s.erase(
 | |
|         s.begin(),
 | |
|         std::find_if_not(s.begin(), s.end(),
 | |
|                          [](char c) { return std::isspace(c);})
 | |
|     );
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief removes leading and trailing whitespace from string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline void
 | |
| trim(std::basic_string<C,T,A>& s)
 | |
| {
 | |
|     triml(s);
 | |
|     trimr(s);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief removes all whitespaces from string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline void
 | |
| remove_ws(std::basic_string<C,T,A>& s)
 | |
| {
 | |
|     if(s.empty()) return;
 | |
| 
 | |
|     s.erase(std::remove_if(s.begin(), s.end(),
 | |
|                            [](char c) { return std::isspace(c); }),
 | |
|             s.end() );
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns true, if the 'prefix' argument
 | |
|  *        is a prefix of the 'subject' argument
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline bool
 | |
| has_prefix(const std::basic_string<C,T,A>& subject,
 | |
|            const std::basic_string<C,T,A>& prefix)
 | |
| {
 | |
|     if(prefix.size() > subject.size()) return false;
 | |
|     return subject.find(prefix) == 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns true, if the 'postfix' argument
 | |
|  *        is a postfix of the 'subject' argument
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline bool
 | |
| has_postfix(const std::basic_string<C,T,A>& subject,
 | |
|             const std::basic_string<C,T,A>& postfix)
 | |
| {
 | |
|     if(postfix.size() > subject.size()) return false;
 | |
|     return (subject.size() - postfix.size()) == subject.find(postfix);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
| *
 | |
| * @brief   returns longest common prefix of several
 | |
| *          sequential random access containers
 | |
| *
 | |
| * @details InputRange require begin and end (member functions or overloads)
 | |
| *          the elements of InputRange require a size() member
 | |
| *
 | |
| *****************************************************************************/
 | |
| template<class InputRange>
 | |
| auto
 | |
| longest_common_prefix(const InputRange& strs)
 | |
|     -> typename std::decay<decltype(*begin(strs))>::type
 | |
| {
 | |
|     static_assert(traits::is_input_range<InputRange>(),
 | |
|         "parameter must satisfy the InputRange concept");
 | |
| 
 | |
|     static_assert(traits::has_size_getter<
 | |
|         typename std::decay<decltype(*begin(strs))>::type>(),
 | |
|         "elements of input range must have a ::size() member function");
 | |
| 
 | |
|     using std::begin;
 | |
|     using std::end;
 | |
| 
 | |
|     using item_t = typename std::decay<decltype(*begin(strs))>::type;
 | |
|     using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type;
 | |
| 
 | |
|     const auto n = size_t(distance(begin(strs), end(strs)));
 | |
|     if(n < 1) return item_t("");
 | |
|     if(n == 1) return *begin(strs);
 | |
| 
 | |
|     //length of shortest string
 | |
|     auto m = std::min_element(begin(strs), end(strs),
 | |
|                 [](const item_t& a, const item_t& b) {
 | |
|                     return a.size() < b.size(); })->size();
 | |
| 
 | |
|     //check each character until we find a mismatch
 | |
|     for(str_size_t i = 0; i < m; ++i) {
 | |
|         for(str_size_t j = 1; j < n; ++j) {
 | |
|             if(strs[j][i] != strs[j-1][i])
 | |
|                 return strs[0].substr(0, i);
 | |
|         }
 | |
|     }
 | |
|     return strs[0].substr(0, m);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief  returns longest substring range that could be found in 'arg'
 | |
|  *
 | |
|  * @param  arg         string to be searched in
 | |
|  * @param  substrings  range of candidate substrings
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A, class InputRange>
 | |
| subrange
 | |
| longest_substring_match(const std::basic_string<C,T,A>& arg,
 | |
|                         const InputRange& substrings)
 | |
| {
 | |
|     using string_t = std::basic_string<C,T,A>;
 | |
| 
 | |
|     static_assert(traits::is_input_range<InputRange>(),
 | |
|         "parameter must satisfy the InputRange concept");
 | |
| 
 | |
|     static_assert(std::is_same<string_t,
 | |
|         typename std::decay<decltype(*begin(substrings))>::type>(),
 | |
|         "substrings must have same type as 'arg'");
 | |
| 
 | |
|     auto i = string_t::npos;
 | |
|     auto n = string_t::size_type(0);
 | |
|     for(const auto& s : substrings) {
 | |
|         auto j = arg.find(s);
 | |
|         if(j != string_t::npos && s.size() > n) {
 | |
|             i = j;
 | |
|             n = s.size();
 | |
|         }
 | |
|     }
 | |
|     return subrange{i,n};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief  returns longest prefix range that could be found in 'arg'
 | |
|  *
 | |
|  * @param  arg       string to be searched in
 | |
|  * @param  prefixes  range of candidate prefix strings
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A, class InputRange>
 | |
| subrange
 | |
| longest_prefix_match(const std::basic_string<C,T,A>& arg,
 | |
|                      const InputRange& prefixes)
 | |
| {
 | |
|     using string_t = std::basic_string<C,T,A>;
 | |
|     using s_size_t = typename string_t::size_type;
 | |
| 
 | |
|     static_assert(traits::is_input_range<InputRange>(),
 | |
|         "parameter must satisfy the InputRange concept");
 | |
| 
 | |
|     static_assert(std::is_same<string_t,
 | |
|         typename std::decay<decltype(*begin(prefixes))>::type>(),
 | |
|         "prefixes must have same type as 'arg'");
 | |
| 
 | |
|     auto i = string_t::npos;
 | |
|     auto n = s_size_t(0);
 | |
|     for(const auto& s : prefixes) {
 | |
|         auto j = arg.find(s);
 | |
|         if(j == 0 && s.size() > n) {
 | |
|             i = 0;
 | |
|             n = s.size();
 | |
|         }
 | |
|     }
 | |
|     return subrange{i,n};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns the first occurrence of 'query' within 'subject'
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| inline subrange
 | |
| substring_match(const std::basic_string<C,T,A>& subject,
 | |
|                 const std::basic_string<C,T,A>& query)
 | |
| {
 | |
|     if(subject.empty() && query.empty()) return subrange(0,0);
 | |
|     if(subject.empty() || query.empty()) return subrange{};
 | |
|     auto i = subject.find(query);
 | |
|     if(i == std::basic_string<C,T,A>::npos) return subrange{};
 | |
|     return subrange{i,query.size()};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns first substring match (pos,len) within the input string
 | |
|  *        that represents a number
 | |
|  *        (with at maximum one decimal point and digit separators)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| subrange
 | |
| first_number_match(std::basic_string<C,T,A> s,
 | |
|                    C digitSeparator = C(','),
 | |
|                    C decimalPoint = C('.'),
 | |
|                    C exponential = C('e'))
 | |
| {
 | |
|     using string_t = std::basic_string<C,T,A>;
 | |
| 
 | |
|     str::trim(s);
 | |
|     if(s.empty()) return subrange{};
 | |
| 
 | |
|     auto i = s.find_first_of("0123456789+-");
 | |
|     if(i == string_t::npos) {
 | |
|         i = s.find(decimalPoint);
 | |
|         if(i == string_t::npos) return subrange{};
 | |
|     }
 | |
| 
 | |
|     bool point = false;
 | |
|     bool sep = false;
 | |
|     auto exp = string_t::npos;
 | |
|     auto j = i + 1;
 | |
|     for(; j < s.size(); ++j) {
 | |
|         if(s[j] == digitSeparator) {
 | |
|             if(!sep) sep = true; else break;
 | |
|         }
 | |
|         else {
 | |
|             sep = false;
 | |
|             if(s[j] == decimalPoint) {
 | |
|                 //only one decimal point before exponent allowed
 | |
|                 if(!point && exp == string_t::npos) point = true; else break;
 | |
|             }
 | |
|             else if(std::tolower(s[j]) == std::tolower(exponential)) {
 | |
|                 //only one exponent separator allowed
 | |
|                 if(exp == string_t::npos) exp = j; else break;
 | |
|             }
 | |
|             else if(exp != string_t::npos && (exp+1) == j) {
 | |
|                 //only sign or digit after exponent separator
 | |
|                 if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break;
 | |
|             }
 | |
|             else if(!std::isdigit(s[j])) {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //if length == 1 then must be a digit
 | |
|     if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
 | |
| 
 | |
|     return subrange{i,j-i};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns first substring match (pos,len)
 | |
|  *        that represents an integer (with optional digit separators)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| subrange
 | |
| first_integer_match(std::basic_string<C,T,A> s,
 | |
|                     C digitSeparator = C(','))
 | |
| {
 | |
|     using string_t = std::basic_string<C,T,A>;
 | |
| 
 | |
|     str::trim(s);
 | |
|     if(s.empty()) return subrange{};
 | |
| 
 | |
|     auto i = s.find_first_of("0123456789+-");
 | |
|     if(i == string_t::npos) return subrange{};
 | |
| 
 | |
|     bool sep = false;
 | |
|     auto j = i + 1;
 | |
|     for(; j < s.size(); ++j) {
 | |
|         if(s[j] == digitSeparator) {
 | |
|             if(!sep) sep = true; else break;
 | |
|         }
 | |
|         else {
 | |
|             sep = false;
 | |
|             if(!std::isdigit(s[j])) break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //if length == 1 then must be a digit
 | |
|     if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
 | |
| 
 | |
|     return subrange{i,j-i};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns true if candidate string represents a number
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| bool represents_number(const std::basic_string<C,T,A>& candidate,
 | |
|                    C digitSeparator = C(','),
 | |
|                    C decimalPoint = C('.'),
 | |
|                    C exponential = C('e'))
 | |
| {
 | |
|     const auto match = str::first_number_match(candidate, digitSeparator,
 | |
|                                                decimalPoint, exponential);
 | |
| 
 | |
|     return (match && match.length() == candidate.size());
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief returns true if candidate string represents an integer
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class C, class T, class A>
 | |
| bool represents_integer(const std::basic_string<C,T,A>& candidate,
 | |
|                         C digitSeparator = C(','))
 | |
| {
 | |
|     const auto match = str::first_integer_match(candidate, digitSeparator);
 | |
|     return (match && match.length() == candidate.size());
 | |
| }
 | |
| 
 | |
| } // namespace str
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object with a const char* parameter
 | |
|  *        that assigns a value to a ref-captured object
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T, class V>
 | |
| inline detail::assign_value<T,V>
 | |
| set(T& target, V value) {
 | |
|     return detail::assign_value<T>{target, std::move(value)};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes parameter-less function object
 | |
|  *        that assigns value(s) to a ref-captured object;
 | |
|  *        value(s) are obtained by converting the const char* argument to
 | |
|  *        the captured object types;
 | |
|  *        bools are always set to true if the argument is not nullptr
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| inline detail::map_arg_to<T>
 | |
| set(T& target) {
 | |
|     return detail::map_arg_to<T>{target};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that sets a bool to true
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline detail::assign_value<bool>
 | |
| set(bool& target) {
 | |
|     return detail::assign_value<bool>{target,true};
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that sets a bool to false
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline detail::assign_value<bool>
 | |
| unset(bool& target) {
 | |
|     return detail::assign_value<bool>{target,false};
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that flips the value of a ref-captured bool
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline detail::flip_bool
 | |
| flip(bool& b) {
 | |
|     return detail::flip_bool(b);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that increments using operator ++
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| inline detail::increment<T>
 | |
| increment(T& target) {
 | |
|     return detail::increment<T>{target};
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that decrements using operator --
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| inline detail::increment_by<T>
 | |
| increment(T& target, T by) {
 | |
|     return detail::increment_by<T>{target, std::move(by)};
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that increments by a fixed amount using operator +=
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| inline detail::decrement<T>
 | |
| decrement(T& target) {
 | |
|     return detail::decrement<T>{target};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace detail {
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief mixin that provides action definition and execution
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Derived>
 | |
| class action_provider
 | |
| {
 | |
| private:
 | |
|     //---------------------------------------------------------------
 | |
|     using simple_action = std::function<void()>;
 | |
|     using arg_action    = std::function<void(const char*)>;
 | |
|     using index_action  = std::function<void(int)>;
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     class simple_action_adapter {
 | |
|     public:
 | |
|         simple_action_adapter() = default;
 | |
|         simple_action_adapter(const simple_action& a): action_(a) {}
 | |
|         simple_action_adapter(simple_action&& a): action_(std::move(a)) {}
 | |
|         void operator() (const char*) const { action_(); }
 | |
|         void operator() (int) const { action_(); }
 | |
|     private:
 | |
|         simple_action action_;
 | |
|     };
 | |
| 
 | |
| 
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds an action that has an operator() that is callable
 | |
|      *         with a 'const char*' argument */
 | |
|     Derived&
 | |
|     call(arg_action a) {
 | |
|         argActions_.push_back(std::move(a));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
|     /** @brief adds an action that has an operator()() */
 | |
|     Derived&
 | |
|     call(simple_action a) {
 | |
|         argActions_.push_back(simple_action_adapter(std::move(a)));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
|     /** @brief adds an action that has an operator() that is callable
 | |
|      *         with a 'const char*' argument */
 | |
|     Derived& operator () (arg_action a)    { return call(std::move(a)); }
 | |
| 
 | |
|     /** @brief adds an action that has an operator()() */
 | |
|     Derived& operator () (simple_action a) { return call(std::move(a)); }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds an action that will set the value of 't' from
 | |
|      *         a 'const char*' arg */
 | |
|     template<class Target>
 | |
|     Derived&
 | |
|     set(Target& t) {
 | |
|         static_assert(!std::is_pointer<Target>::value,
 | |
|                       "parameter target type must not be a pointer");
 | |
| 
 | |
|         return call(clipp::set(t));
 | |
|     }
 | |
| 
 | |
|     /** @brief adds an action that will set the value of 't' to 'v' */
 | |
|     template<class Target, class Value>
 | |
|     Derived&
 | |
|     set(Target& t, Value&& v) {
 | |
|         return call(clipp::set(t, std::forward<Value>(v)));
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds an action that will be called if a parameter
 | |
|      *         matches an argument for the 2nd, 3rd, 4th, ... time
 | |
|      */
 | |
|     Derived&
 | |
|     if_repeated(simple_action a) {
 | |
|         repeatActions_.push_back(simple_action_adapter{std::move(a)});
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
|     /** @brief adds an action that will be called with the argument's
 | |
|      *         index if a parameter matches an argument for
 | |
|      *         the 2nd, 3rd, 4th, ... time
 | |
|      */
 | |
|     Derived&
 | |
|     if_repeated(index_action a) {
 | |
|         repeatActions_.push_back(std::move(a));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds an action that will be called if a required parameter
 | |
|      *         is missing
 | |
|      */
 | |
|     Derived&
 | |
|     if_missing(simple_action a) {
 | |
|         missingActions_.push_back(simple_action_adapter{std::move(a)});
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
|     /** @brief adds an action that will be called if a required parameter
 | |
|      *         is missing; the action will get called with the index of
 | |
|      *         the command line argument where the missing event occurred first
 | |
|      */
 | |
|     Derived&
 | |
|     if_missing(index_action a) {
 | |
|         missingActions_.push_back(std::move(a));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds an action that will be called if a parameter
 | |
|      *         was matched, but was unreachable in the current scope
 | |
|      */
 | |
|     Derived&
 | |
|     if_blocked(simple_action a) {
 | |
|         blockedActions_.push_back(simple_action_adapter{std::move(a)});
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
|     /** @brief adds an action that will be called if a parameter
 | |
|      *         was matched, but was unreachable in the current scope;
 | |
|      *         the action will be called with the index of
 | |
|      *         the command line argument where the problem occurred
 | |
|      */
 | |
|     Derived&
 | |
|     if_blocked(index_action a) {
 | |
|         blockedActions_.push_back(std::move(a));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds an action that will be called if a parameter match
 | |
|      *         was in conflict with a different alternative parameter
 | |
|      */
 | |
|     Derived&
 | |
|     if_conflicted(simple_action a) {
 | |
|         conflictActions_.push_back(simple_action_adapter{std::move(a)});
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
|     /** @brief adds an action that will be called if a parameter match
 | |
|      *         was in conflict with a different alternative parameter;
 | |
|      *         the action will be called with the index of
 | |
|      *         the command line argument where the problem occurred
 | |
|      */
 | |
|     Derived&
 | |
|     if_conflicted(index_action a) {
 | |
|         conflictActions_.push_back(std::move(a));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds targets = either objects whose values should be
 | |
|      *         set by command line arguments or actions that should
 | |
|      *         be called in case of a match */
 | |
|     template<class T, class... Ts>
 | |
|     Derived&
 | |
|     target(T&& t, Ts&&... ts) {
 | |
|         target(std::forward<T>(t));
 | |
|         target(std::forward<Ts>(ts)...);
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
|     /** @brief adds action that should be called in case of a match */
 | |
|     template<class T, class = typename std::enable_if<
 | |
|             !std::is_fundamental<typename std::decay<T>::type>() &&
 | |
|             (traits::is_callable<T,void()>() ||
 | |
|              traits::is_callable<T,void(const char*)>() )
 | |
|         >::type>
 | |
|     Derived&
 | |
|     target(T&& t) {
 | |
|         call(std::forward<T>(t));
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
|     /** @brief adds object whose value should be set by command line arguments
 | |
|      */
 | |
|     template<class T, class = typename std::enable_if<
 | |
|             std::is_fundamental<typename std::decay<T>::type>() ||
 | |
|             (!traits::is_callable<T,void()>() &&
 | |
|              !traits::is_callable<T,void(const char*)>() )
 | |
|         >::type>
 | |
|     Derived&
 | |
|     target(T& t) {
 | |
|         set(t);
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
|     //TODO remove ugly empty param list overload
 | |
|     Derived&
 | |
|     target() {
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds target, see member function 'target' */
 | |
|     template<class Target>
 | |
|     inline friend Derived&
 | |
|     operator << (Target&& t, Derived& p) {
 | |
|         p.target(std::forward<Target>(t));
 | |
|         return p;
 | |
|     }
 | |
|     /** @brief adds target, see member function 'target' */
 | |
|     template<class Target>
 | |
|     inline friend Derived&&
 | |
|     operator << (Target&& t, Derived&& p) {
 | |
|         p.target(std::forward<Target>(t));
 | |
|         return std::move(p);
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds target, see member function 'target' */
 | |
|     template<class Target>
 | |
|     inline friend Derived&
 | |
|     operator >> (Derived& p, Target&& t) {
 | |
|         p.target(std::forward<Target>(t));
 | |
|         return p;
 | |
|     }
 | |
|     /** @brief adds target, see member function 'target' */
 | |
|     template<class Target>
 | |
|     inline friend Derived&&
 | |
|     operator >> (Derived&& p, Target&& t) {
 | |
|         p.target(std::forward<Target>(t));
 | |
|         return std::move(p);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief executes all argument actions */
 | |
|     void execute_actions(const arg_string& arg) const {
 | |
|         int i = 0;
 | |
|         for(const auto& a : argActions_) {
 | |
|             ++i;
 | |
|             a(arg.c_str());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /** @brief executes repeat actions */
 | |
|     void notify_repeated(arg_index idx) const {
 | |
|         for(const auto& a : repeatActions_) a(idx);
 | |
|     }
 | |
|     /** @brief executes missing error actions */
 | |
|     void notify_missing(arg_index idx) const {
 | |
|         for(const auto& a : missingActions_) a(idx);
 | |
|     }
 | |
|     /** @brief executes blocked error actions */
 | |
|     void notify_blocked(arg_index idx) const {
 | |
|         for(const auto& a : blockedActions_) a(idx);
 | |
|     }
 | |
|     /** @brief executes conflict error actions */
 | |
|     void notify_conflict(arg_index idx) const {
 | |
|         for(const auto& a : conflictActions_) a(idx);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     //---------------------------------------------------------------
 | |
|     std::vector<arg_action> argActions_;
 | |
|     std::vector<index_action> repeatActions_;
 | |
|     std::vector<index_action> missingActions_;
 | |
|     std::vector<index_action> blockedActions_;
 | |
|     std::vector<index_action> conflictActions_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief mixin that provides basic common settings of parameters and groups
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Derived>
 | |
| class token
 | |
| {
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     using doc_string = clipp::doc_string;
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns documentation string */
 | |
|     const doc_string& doc() const noexcept {
 | |
|         return doc_;
 | |
|     }
 | |
| 
 | |
|     /** @brief sets documentations string */
 | |
|     Derived& doc(const doc_string& txt) {
 | |
|         doc_ = txt;
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
|     /** @brief sets documentations string */
 | |
|     Derived& doc(doc_string&& txt) {
 | |
|         doc_ = std::move(txt);
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns if a group/parameter is repeatable */
 | |
|     bool repeatable() const noexcept {
 | |
|         return repeatable_;
 | |
|     }
 | |
| 
 | |
|     /** @brief sets repeatability of group/parameter */
 | |
|     Derived& repeatable(bool yes) noexcept {
 | |
|         repeatable_ = yes;
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns if a group/parameter is blocking/positional */
 | |
|     bool blocking() const noexcept {
 | |
|         return blocking_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines, if a group/parameter is blocking/positional */
 | |
|     Derived& blocking(bool yes) noexcept {
 | |
|         blocking_ = yes;
 | |
|         return *static_cast<Derived*>(this);
 | |
|     }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     //---------------------------------------------------------------
 | |
|     doc_string doc_;
 | |
|     bool repeatable_ = false;
 | |
|     bool blocking_ = false;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief sets documentation strings on a token
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| inline T&
 | |
| operator % (doc_string docstr, token<T>& p)
 | |
| {
 | |
|     return p.doc(std::move(docstr));
 | |
| }
 | |
| //---------------------------------------------------------
 | |
| template<class T>
 | |
| inline T&&
 | |
| operator % (doc_string docstr, token<T>&& p)
 | |
| {
 | |
|     return std::move(p.doc(std::move(docstr)));
 | |
| }
 | |
| 
 | |
| //---------------------------------------------------------
 | |
| template<class T>
 | |
| inline T&
 | |
| operator % (token<T>& p, doc_string docstr)
 | |
| {
 | |
|     return p.doc(std::move(docstr));
 | |
| }
 | |
| //---------------------------------------------------------
 | |
| template<class T>
 | |
| inline T&&
 | |
| operator % (token<T>&& p, doc_string docstr)
 | |
| {
 | |
|     return std::move(p.doc(std::move(docstr)));
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief sets documentation strings on a token
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class T>
 | |
| inline T&
 | |
| doc(doc_string docstr, token<T>& p)
 | |
| {
 | |
|     return p.doc(std::move(docstr));
 | |
| }
 | |
| //---------------------------------------------------------
 | |
| template<class T>
 | |
| inline T&&
 | |
| doc(doc_string docstr, token<T>&& p)
 | |
| {
 | |
|     return std::move(p.doc(std::move(docstr)));
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| } // namespace detail
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief contains parameter matching functions and function classes
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace match {
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that is always true
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline bool
 | |
| any(const arg_string&) { return true; }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that is always false
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline bool
 | |
| none(const arg_string&) { return false; }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the argument string is non-empty string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline bool
 | |
| nonempty(const arg_string& s) {
 | |
|     return !s.empty();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the argument is a non-empty
 | |
|  *        string that consists only of alphanumeric characters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline bool
 | |
| alphanumeric(const arg_string& s) {
 | |
|     if(s.empty()) return false;
 | |
|     return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); });
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the argument is a non-empty
 | |
|  *        string that consists only of alphabetic characters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline bool
 | |
| alphabetic(const arg_string& s) {
 | |
|     return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); });
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns false if the argument string is
 | |
|  *        equal to any string from the exclusion list
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class none_of
 | |
| {
 | |
| public:
 | |
|     none_of(arg_list strs):
 | |
|         excluded_{std::move(strs)}
 | |
|     {}
 | |
| 
 | |
|     template<class... Strings>
 | |
|     none_of(arg_string str, Strings&&... strs):
 | |
|         excluded_{std::move(str), std::forward<Strings>(strs)...}
 | |
|     {}
 | |
| 
 | |
|     template<class... Strings>
 | |
|     none_of(const char* str, Strings&&... strs):
 | |
|         excluded_{arg_string(str), std::forward<Strings>(strs)...}
 | |
|     {}
 | |
| 
 | |
|     bool operator () (const arg_string& arg) const {
 | |
|         return (std::find(begin(excluded_), end(excluded_), arg)
 | |
|                 == end(excluded_));
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     arg_list excluded_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns the first substring match within the input
 | |
|  *        string that rmeepresents a number
 | |
|  *        (with at maximum one decimal point and digit separators)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class numbers
 | |
| {
 | |
| public:
 | |
|     explicit
 | |
|     numbers(char decimalPoint = '.',
 | |
|             char digitSeparator = ' ',
 | |
|             char exponentSeparator = 'e')
 | |
|     :
 | |
|         decpoint_{decimalPoint}, separator_{digitSeparator},
 | |
|         exp_{exponentSeparator}
 | |
|     {}
 | |
| 
 | |
|     subrange operator () (const arg_string& s) const {
 | |
|         return str::first_number_match(s, separator_, decpoint_, exp_);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     char decpoint_;
 | |
|     char separator_;
 | |
|     char exp_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the input string represents an integer
 | |
|  *        (with optional digit separators)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class integers {
 | |
| public:
 | |
|     explicit
 | |
|     integers(char digitSeparator = ' '): separator_{digitSeparator} {}
 | |
| 
 | |
|     subrange operator () (const arg_string& s) const {
 | |
|         return str::first_integer_match(s, separator_);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     char separator_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the input string represents
 | |
|  *        a non-negative integer (with optional digit separators)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class positive_integers {
 | |
| public:
 | |
|     explicit
 | |
|     positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {}
 | |
| 
 | |
|     subrange operator () (const arg_string& s) const {
 | |
|         auto match = str::first_integer_match(s, separator_);
 | |
|         if(!match) return subrange{};
 | |
|         if(s[match.at()] == '-') return subrange{};
 | |
|         return match;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     char separator_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the input string
 | |
|  *        contains a given substring
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class substring
 | |
| {
 | |
| public:
 | |
|     explicit
 | |
|     substring(arg_string str): str_{std::move(str)} {}
 | |
| 
 | |
|     subrange operator () (const arg_string& s) const {
 | |
|         return str::substring_match(s, str_);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     arg_string str_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the input string starts
 | |
|  *        with a given prefix
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class prefix {
 | |
| public:
 | |
|     explicit
 | |
|     prefix(arg_string p): prefix_{std::move(p)} {}
 | |
| 
 | |
|     bool operator () (const arg_string& s) const {
 | |
|         return s.find(prefix_) == 0;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     arg_string prefix_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the input string does not start
 | |
|  *        with a given prefix
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class prefix_not {
 | |
| public:
 | |
|     explicit
 | |
|     prefix_not(arg_string p): prefix_{std::move(p)} {}
 | |
| 
 | |
|     bool operator () (const arg_string& s) const {
 | |
|         return s.find(prefix_) != 0;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     arg_string prefix_;
 | |
| };
 | |
| 
 | |
| 
 | |
| /** @brief alias for prefix_not */
 | |
| using noprefix = prefix_not;
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief predicate that returns true if the length of the input string
 | |
|  *        is wihtin a given interval
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class length {
 | |
| public:
 | |
|     explicit
 | |
|     length(std::size_t exact):
 | |
|         min_{exact}, max_{exact}
 | |
|     {}
 | |
| 
 | |
|     explicit
 | |
|     length(std::size_t min, std::size_t max):
 | |
|         min_{min}, max_{max}
 | |
|     {}
 | |
| 
 | |
|     bool operator () (const arg_string& s) const {
 | |
|         return s.size() >= min_ && s.size() <= max_;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     std::size_t min_;
 | |
|     std::size_t max_;
 | |
| };
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that returns true if the input string has a
 | |
|  *        given minimum length
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline length min_length(std::size_t min)
 | |
| {
 | |
|     return length{min, arg_string::npos-1};
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes function object that returns true if the input string is
 | |
|  *        not longer than a given maximum length
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline length max_length(std::size_t max)
 | |
| {
 | |
|     return length{0, max};
 | |
| }
 | |
| 
 | |
| 
 | |
| } // namespace match
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief command line parameter that can match one or many arguments.
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class parameter :
 | |
|     public detail::token<parameter>,
 | |
|     public detail::action_provider<parameter>
 | |
| {
 | |
|     /** @brief adapts a 'match_predicate' to the 'match_function' interface */
 | |
|     class predicate_adapter {
 | |
|     public:
 | |
|         explicit
 | |
|         predicate_adapter(match_predicate pred): match_{std::move(pred)} {}
 | |
| 
 | |
|         subrange operator () (const arg_string& arg) const {
 | |
|             return match_(arg) ? subrange{0,arg.size()} : subrange{};
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         match_predicate match_;
 | |
|     };
 | |
| 
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief makes default parameter, that will match nothing */
 | |
|     parameter():
 | |
|         flags_{},
 | |
|         matcher_{predicate_adapter{match::none}},
 | |
|         label_{}, required_{false}, greedy_{false}
 | |
|     {}
 | |
| 
 | |
|     /** @brief makes "flag" parameter */
 | |
|     template<class... Strings>
 | |
|     explicit
 | |
|     parameter(arg_string str, Strings&&... strs):
 | |
|         flags_{},
 | |
|         matcher_{predicate_adapter{match::none}},
 | |
|         label_{}, required_{false}, greedy_{false}
 | |
|     {
 | |
|         add_flags(std::move(str), std::forward<Strings>(strs)...);
 | |
|     }
 | |
| 
 | |
|     /** @brief makes "flag" parameter from range of strings */
 | |
|     explicit
 | |
|     parameter(const arg_list& flaglist):
 | |
|         flags_{},
 | |
|         matcher_{predicate_adapter{match::none}},
 | |
|         label_{}, required_{false}, greedy_{false}
 | |
|     {
 | |
|         add_flags(flaglist);
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief makes "value" parameter with custom match predicate
 | |
|      *         (= yes/no matcher)
 | |
|      */
 | |
|     explicit
 | |
|     parameter(match_predicate filter):
 | |
|         flags_{},
 | |
|         matcher_{predicate_adapter{std::move(filter)}},
 | |
|         label_{}, required_{false}, greedy_{false}
 | |
|     {}
 | |
| 
 | |
|     /** @brief makes "value" parameter with custom match function
 | |
|      *         (= partial matcher)
 | |
|      */
 | |
|     explicit
 | |
|     parameter(match_function filter):
 | |
|         flags_{},
 | |
|         matcher_{std::move(filter)},
 | |
|         label_{}, required_{false}, greedy_{false}
 | |
|     {}
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns if a parameter is required */
 | |
|     bool
 | |
|     required() const noexcept {
 | |
|         return required_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines if a parameter is required */
 | |
|     parameter&
 | |
|     required(bool yes) noexcept {
 | |
|         required_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns if a parameter should match greedily */
 | |
|     bool
 | |
|     greedy() const noexcept {
 | |
|         return greedy_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines if a parameter should match greedily */
 | |
|     parameter&
 | |
|     greedy(bool yes) noexcept {
 | |
|         greedy_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns parameter label;
 | |
|      *         will be used for documentation, if flags are empty
 | |
|      */
 | |
|     const doc_string&
 | |
|     label() const {
 | |
|         return label_;
 | |
|     }
 | |
| 
 | |
|     /** @brief sets parameter label;
 | |
|      *         will be used for documentation, if flags are empty
 | |
|      */
 | |
|     parameter&
 | |
|     label(const doc_string& lbl) {
 | |
|         label_ = lbl;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     /** @brief sets parameter label;
 | |
|      *         will be used for documentation, if flags are empty
 | |
|      */
 | |
|     parameter&
 | |
|     label(doc_string&& lbl) {
 | |
|         label_ = lbl;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns either longest matching prefix of 'arg' in any
 | |
|      *         of the flags or the result of the custom match operation
 | |
|      */
 | |
|     subrange
 | |
|     match(const arg_string& arg) const
 | |
|     {
 | |
|         if(flags_.empty()) {
 | |
|             return matcher_(arg);
 | |
|         }
 | |
|         else {
 | |
|             //empty flags are not allowed
 | |
|             if(arg.empty()) return subrange{};
 | |
| 
 | |
|             if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) {
 | |
|                 return subrange{0,arg.size()};
 | |
|             }
 | |
|             return str::longest_prefix_match(arg, flags_);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief access range of flag strings */
 | |
|     const arg_list&
 | |
|     flags() const noexcept {
 | |
|         return flags_;
 | |
|     }
 | |
| 
 | |
|     /** @brief access custom match operation */
 | |
|     const match_function&
 | |
|     matcher() const noexcept {
 | |
|         return matcher_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief prepend prefix to each flag */
 | |
|     inline friend parameter&
 | |
|     with_prefix(const arg_string& prefix, parameter& p)
 | |
|     {
 | |
|         if(prefix.empty() || p.flags().empty()) return p;
 | |
| 
 | |
|         for(auto& f : p.flags_) {
 | |
|             if(f.find(prefix) != 0) f.insert(0, prefix);
 | |
|         }
 | |
|         return p;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /** @brief prepend prefix to each flag
 | |
|      */
 | |
|     inline friend parameter&
 | |
|     with_prefixes_short_long(
 | |
|         const arg_string& shortpfx, const arg_string& longpfx,
 | |
|         parameter& p)
 | |
|     {
 | |
|         if(shortpfx.empty() && longpfx.empty()) return p;
 | |
|         if(p.flags().empty()) return p;
 | |
| 
 | |
|         for(auto& f : p.flags_) {
 | |
|             if(f.size() == 1) {
 | |
|                 if(f.find(shortpfx) != 0) f.insert(0, shortpfx);
 | |
|             } else {
 | |
|                 if(f.find(longpfx) != 0) f.insert(0, longpfx);
 | |
|             }
 | |
|         }
 | |
|         return p;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief prepend suffix to each flag */
 | |
|     inline friend parameter&
 | |
|     with_suffix(const arg_string& suffix, parameter& p)
 | |
|     {
 | |
|         if(suffix.empty() || p.flags().empty()) return p;
 | |
| 
 | |
|         for(auto& f : p.flags_) {
 | |
|             if(f.find(suffix) + suffix.size() != f.size()) {
 | |
|                 f.insert(f.end(), suffix.begin(), suffix.end());
 | |
|             }
 | |
|         }
 | |
|         return p;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /** @brief prepend suffix to each flag
 | |
|      */
 | |
|     inline friend parameter&
 | |
|     with_suffixes_short_long(
 | |
|         const arg_string& shortsfx, const arg_string& longsfx,
 | |
|         parameter& p)
 | |
|     {
 | |
|         if(shortsfx.empty() && longsfx.empty()) return p;
 | |
|         if(p.flags().empty()) return p;
 | |
| 
 | |
|         for(auto& f : p.flags_) {
 | |
|             if(f.size() == 1) {
 | |
|                 if(f.find(shortsfx) + shortsfx.size() != f.size()) {
 | |
|                     f.insert(f.end(), shortsfx.begin(), shortsfx.end());
 | |
|                 }
 | |
|             } else {
 | |
|                 if(f.find(longsfx) + longsfx.size() != f.size()) {
 | |
|                     f.insert(f.end(), longsfx.begin(), longsfx.end());
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return p;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     //---------------------------------------------------------------
 | |
|     void add_flags(arg_string str) {
 | |
|         //empty flags are not allowed
 | |
|         str::remove_ws(str);
 | |
|         if(!str.empty()) flags_.push_back(std::move(str));
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     void add_flags(const arg_list& strs) {
 | |
|         if(strs.empty()) return;
 | |
|         flags_.reserve(flags_.size() + strs.size());
 | |
|         for(const auto& s : strs) add_flags(s);
 | |
|     }
 | |
| 
 | |
|     template<class String1, class String2, class... Strings>
 | |
|     void
 | |
|     add_flags(String1&& s1, String2&& s2, Strings&&... ss) {
 | |
|         flags_.reserve(2 + sizeof...(ss));
 | |
|         add_flags(std::forward<String1>(s1));
 | |
|         add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...);
 | |
|     }
 | |
| 
 | |
|     arg_list flags_;
 | |
|     match_function matcher_;
 | |
|     doc_string label_;
 | |
|     bool required_ = false;
 | |
|     bool greedy_ = false;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required non-blocking exact match parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class String, class... Strings>
 | |
| inline parameter
 | |
| command(String&& flag, Strings&&... flags)
 | |
| {
 | |
|     return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
 | |
|         .required(true).blocking(true).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required non-blocking exact match parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class String, class... Strings>
 | |
| inline parameter
 | |
| required(String&& flag, Strings&&... flags)
 | |
| {
 | |
|     return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
 | |
|         .required(true).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, non-blocking exact match parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class String, class... Strings>
 | |
| inline parameter
 | |
| option(String&& flag, Strings&&... flags)
 | |
| {
 | |
|     return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
 | |
|         .required(false).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking, repeatable value parameter;
 | |
|  *        matches any non-empty string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| value(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::nonempty}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(false);
 | |
| }
 | |
| 
 | |
| template<class Filter, class... Targets, class = typename std::enable_if<
 | |
|     traits::is_callable<Filter,bool(const char*)>::value ||
 | |
|     traits::is_callable<Filter,subrange(const char*)>::value>::type>
 | |
| inline parameter
 | |
| value(Filter&& filter, doc_string label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{std::forward<Filter>(filter)}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking, repeatable value parameter;
 | |
|  *        matches any non-empty string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| values(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::nonempty}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(true);
 | |
| }
 | |
| 
 | |
| template<class Filter, class... Targets, class = typename std::enable_if<
 | |
|     traits::is_callable<Filter,bool(const char*)>::value ||
 | |
|     traits::is_callable<Filter,subrange(const char*)>::value>::type>
 | |
| inline parameter
 | |
| values(Filter&& filter, doc_string label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{std::forward<Filter>(filter)}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking value parameter;
 | |
|  *        matches any non-empty string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_value(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::nonempty}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| template<class Filter, class... Targets, class = typename std::enable_if<
 | |
|     traits::is_callable<Filter,bool(const char*)>::value ||
 | |
|     traits::is_callable<Filter,subrange(const char*)>::value>::type>
 | |
| inline parameter
 | |
| opt_value(Filter&& filter, doc_string label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{std::forward<Filter>(filter)}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking, repeatable value parameter;
 | |
|  *        matches any non-empty string
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_values(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::nonempty}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| template<class Filter, class... Targets, class = typename std::enable_if<
 | |
|     traits::is_callable<Filter,bool(const char*)>::value ||
 | |
|     traits::is_callable<Filter,subrange(const char*)>::value>::type>
 | |
| inline parameter
 | |
| opt_values(Filter&& filter, doc_string label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{std::forward<Filter>(filter)}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking value parameter;
 | |
|  *        matches any string consisting of alphanumeric characters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| word(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::alphanumeric}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking, repeatable value parameter;
 | |
|  *        matches any string consisting of alphanumeric characters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| words(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::alphanumeric}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking value parameter;
 | |
|  *        matches any string consisting of alphanumeric characters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_word(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::alphanumeric}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking, repeatable value parameter;
 | |
|  *        matches any string consisting of alphanumeric characters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_words(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::alphanumeric}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking value parameter;
 | |
|  *        matches any string that represents a number
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| number(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::numbers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking, repeatable value parameter;
 | |
|  *        matches any string that represents a number
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| numbers(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::numbers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking value parameter;
 | |
|  *        matches any string that represents a number
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_number(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::numbers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking, repeatable value parameter;
 | |
|  *        matches any string that represents a number
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_numbers(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::numbers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking value parameter;
 | |
|  *        matches any string that represents an integer
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| integer(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::integers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes required, blocking, repeatable value parameter;
 | |
|  *        matches any string that represents an integer
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| integers(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::integers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(true).blocking(true).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking value parameter;
 | |
|  *        matches any string that represents an integer
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_integer(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::integers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes optional, blocking, repeatable value parameter;
 | |
|  *        matches any string that represents an integer
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| opt_integers(const doc_string& label, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::integers{}}
 | |
|         .label(label)
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes catch-all value parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class... Targets>
 | |
| inline parameter
 | |
| any_other(Targets&&... tgts)
 | |
| {
 | |
|     return parameter{match::any}
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes catch-all value parameter with custom filter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Filter, class... Targets, class = typename std::enable_if<
 | |
|     traits::is_callable<Filter,bool(const char*)>::value ||
 | |
|     traits::is_callable<Filter,subrange(const char*)>::value>::type>
 | |
| inline parameter
 | |
| any(Filter&& filter, Targets&&... tgts)
 | |
| {
 | |
|     return parameter{std::forward<Filter>(filter)}
 | |
|         .target(std::forward<Targets>(tgts)...)
 | |
|         .required(false).blocking(false).repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief group of parameters and/or other groups;
 | |
|  *        can be configured to act as a group of alternatives (exclusive match)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class group :
 | |
|     public detail::token<group>
 | |
| {
 | |
|     //---------------------------------------------------------------
 | |
|     /**
 | |
|         * @brief tagged union type that either stores a parameter or a group
 | |
|         *        and provides a common interface to them
 | |
|         *        could be replaced by std::variant in the future
 | |
|         *
 | |
|         *        Note to future self: do NOT try again to do this with
 | |
|         *        dynamic polymorphism; there are a couple of
 | |
|         *        nasty problems associated with it and the implementation
 | |
|         *        becomes bloated and needlessly complicated.
 | |
|         */
 | |
|     template<class Param, class Group>
 | |
|     struct child_t {
 | |
|         enum class type : char {param, group};
 | |
|     public:
 | |
| 
 | |
|         explicit
 | |
|         child_t(const Param&  v)          : m_{v},            type_{type::param} {}
 | |
|         child_t(      Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {}
 | |
| 
 | |
|         explicit
 | |
|         child_t(const Group&  g)          : m_{g},            type_{type::group} {}
 | |
|         child_t(      Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {}
 | |
| 
 | |
|         child_t(const child_t& src): type_{src.type_} {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::param: new(&m_)data{src.m_.param}; break;
 | |
|                 case type::group: new(&m_)data{src.m_.group}; break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         child_t(child_t&& src) noexcept : type_{src.type_} {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::param: new(&m_)data{std::move(src.m_.param)}; break;
 | |
|                 case type::group: new(&m_)data{std::move(src.m_.group)}; break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         child_t& operator = (const child_t& src) {
 | |
|             destroy_content();
 | |
|             type_ = src.type_;
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::param: new(&m_)data{src.m_.param}; break;
 | |
|                 case type::group: new(&m_)data{src.m_.group}; break;
 | |
|             }
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         child_t& operator = (child_t&& src) noexcept {
 | |
|             destroy_content();
 | |
|             type_ = src.type_;
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::param: new(&m_)data{std::move(src.m_.param)}; break;
 | |
|                 case type::group: new(&m_)data{std::move(src.m_.group)}; break;
 | |
|             }
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         ~child_t() {
 | |
|             destroy_content();
 | |
|         }
 | |
| 
 | |
|         const doc_string&
 | |
|         doc() const noexcept {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::param: return m_.param.doc();
 | |
|                 case type::group: return m_.group.doc();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         bool blocking() const noexcept {
 | |
|             switch(type_) {
 | |
|                 case type::param: return m_.param.blocking();
 | |
|                 case type::group: return m_.group.blocking();
 | |
|                 default: return false;
 | |
|             }
 | |
|         }
 | |
|         bool repeatable() const noexcept {
 | |
|             switch(type_) {
 | |
|                 case type::param: return m_.param.repeatable();
 | |
|                 case type::group: return m_.group.repeatable();
 | |
|                 default: return false;
 | |
|             }
 | |
|         }
 | |
|         bool required() const noexcept {
 | |
|             switch(type_) {
 | |
|                 case type::param: return m_.param.required();
 | |
|                 case type::group:
 | |
|                     return (m_.group.exclusive() && m_.group.all_required() ) ||
 | |
|                           (!m_.group.exclusive() && m_.group.any_required()  );
 | |
|                 default: return false;
 | |
|             }
 | |
|         }
 | |
|         bool exclusive() const noexcept {
 | |
|             switch(type_) {
 | |
|                 case type::group: return m_.group.exclusive();
 | |
|                 case type::param:
 | |
|                 default: return false;
 | |
|             }
 | |
|         }
 | |
|         std::size_t param_count() const noexcept {
 | |
|             switch(type_) {
 | |
|                 case type::group: return m_.group.param_count();
 | |
|                 case type::param:
 | |
|                 default: return std::size_t(1);
 | |
|             }
 | |
|         }
 | |
|         std::size_t depth() const noexcept {
 | |
|             switch(type_) {
 | |
|                 case type::group: return m_.group.depth();
 | |
|                 case type::param:
 | |
|                 default: return std::size_t(0);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void execute_actions(const arg_string& arg) const {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::group: return;
 | |
|                 case type::param: m_.param.execute_actions(arg); break;
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         void notify_repeated(arg_index idx) const {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::group: return;
 | |
|                 case type::param: m_.param.notify_repeated(idx); break;
 | |
|             }
 | |
|         }
 | |
|         void notify_missing(arg_index idx) const {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::group: return;
 | |
|                 case type::param: m_.param.notify_missing(idx); break;
 | |
|             }
 | |
|         }
 | |
|         void notify_blocked(arg_index idx) const {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::group: return;
 | |
|                 case type::param: m_.param.notify_blocked(idx); break;
 | |
|             }
 | |
|         }
 | |
|         void notify_conflict(arg_index idx) const {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::group: return;
 | |
|                 case type::param: m_.param.notify_conflict(idx); break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         bool is_param() const noexcept { return type_ == type::param; }
 | |
|         bool is_group() const noexcept { return type_ == type::group; }
 | |
| 
 | |
|         Param& as_param() noexcept { return m_.param; }
 | |
|         Group& as_group() noexcept { return m_.group; }
 | |
| 
 | |
|         const Param& as_param() const noexcept { return m_.param; }
 | |
|         const Group& as_group() const noexcept { return m_.group; }
 | |
| 
 | |
|     private:
 | |
|         void destroy_content() {
 | |
|             switch(type_) {
 | |
|                 default:
 | |
|                 case type::param: m_.param.~Param(); break;
 | |
|                 case type::group: m_.group.~Group(); break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         union data {
 | |
|             data() {}
 | |
| 
 | |
|             data(const Param&  v)          : param{v} {}
 | |
|             data(      Param&& v) noexcept : param{std::move(v)} {}
 | |
| 
 | |
|             data(const Group&  g)          : group{g} {}
 | |
|             data(      Group&& g) noexcept : group{std::move(g)} {}
 | |
|             ~data() {}
 | |
| 
 | |
|             Param param;
 | |
|             Group group;
 | |
|         };
 | |
| 
 | |
|         data m_;
 | |
|         type type_;
 | |
|     };
 | |
| 
 | |
| 
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     using child      = child_t<parameter,group>;
 | |
|     using value_type = child;
 | |
| 
 | |
| private:
 | |
|     using children_store = std::vector<child>;
 | |
| 
 | |
| public:
 | |
|     using const_iterator = children_store::const_iterator;
 | |
|     using iterator       = children_store::iterator;
 | |
|     using size_type      = children_store::size_type;
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /**
 | |
|      * @brief recursively iterates over all nodes
 | |
|      */
 | |
|     class depth_first_traverser
 | |
|     {
 | |
|     public:
 | |
|         //-----------------------------------------------------
 | |
|         struct context {
 | |
|             context() = default;
 | |
|             context(const group& p):
 | |
|                 parent{&p}, cur{p.begin()}, end{p.end()}
 | |
|             {}
 | |
|             const group* parent = nullptr;
 | |
|             const_iterator cur;
 | |
|             const_iterator end;
 | |
|         };
 | |
|         using context_list = std::vector<context>;
 | |
| 
 | |
|         //-----------------------------------------------------
 | |
|         class memento {
 | |
|             friend class depth_first_traverser;
 | |
|             int level_;
 | |
|             context context_;
 | |
|         public:
 | |
|             int level() const noexcept { return level_; }
 | |
|             const child* param() const noexcept { return &(*context_.cur); }
 | |
|         };
 | |
| 
 | |
|         depth_first_traverser() = default;
 | |
| 
 | |
|         explicit
 | |
|         depth_first_traverser(const group& cur): stack_{} {
 | |
|             if(!cur.empty()) stack_.emplace_back(cur);
 | |
|         }
 | |
| 
 | |
|         explicit operator bool() const noexcept {
 | |
|             return !stack_.empty();
 | |
|         }
 | |
| 
 | |
|         int level() const noexcept {
 | |
|             return int(stack_.size());
 | |
|         }
 | |
| 
 | |
|         bool is_first_in_parent() const noexcept {
 | |
|             if(stack_.empty()) return false;
 | |
|             return (stack_.back().cur == stack_.back().parent->begin());
 | |
|         }
 | |
| 
 | |
|         bool is_last_in_parent() const noexcept {
 | |
|             if(stack_.empty()) return false;
 | |
|             return (stack_.back().cur+1 == stack_.back().end);
 | |
|         }
 | |
| 
 | |
|         bool is_last_in_path() const noexcept {
 | |
|             if(stack_.empty()) return false;
 | |
|             for(const auto& t : stack_) {
 | |
|                 if(t.cur+1 != t.end) return false;
 | |
|             }
 | |
|             const auto& top = stack_.back();
 | |
|             //if we have to descend into group on next ++ => not last in path
 | |
|             if(top.cur->is_group()) return false;
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         /** @brief inside a group of alternatives >= minlevel */
 | |
|         bool is_alternative(int minlevel = 0) const noexcept {
 | |
|             if(stack_.empty()) return false;
 | |
|             if(minlevel > 0) minlevel -= 1;
 | |
|             if(minlevel >= int(stack_.size())) return false;
 | |
|             return std::any_of(stack_.begin() + minlevel, stack_.end(),
 | |
|                 [](const context& c) { return c.parent->exclusive(); });
 | |
|         }
 | |
| 
 | |
|         /** @brief repeatable or inside a repeatable group >= minlevel */
 | |
|         bool is_repeatable(int minlevel = 0) const noexcept {
 | |
|             if(stack_.empty()) return false;
 | |
|             if(stack_.back().cur->repeatable()) return true;
 | |
|             if(minlevel > 0) minlevel -= 1;
 | |
|             if(minlevel >= int(stack_.size())) return false;
 | |
|             return std::any_of(stack_.begin() + minlevel, stack_.end(),
 | |
|                 [](const context& c) { return c.parent->repeatable(); });
 | |
|         }
 | |
| 
 | |
|         /** @brief inside a particular group */
 | |
|         bool is_inside(const group* g) const noexcept {
 | |
|             if(!g) return false;
 | |
|             return std::any_of(stack_.begin(), stack_.end(),
 | |
|                 [g](const context& c) { return c.parent == g; });
 | |
|         }
 | |
| 
 | |
|         /** @brief inside group with joinable flags */
 | |
|         bool joinable() const noexcept {
 | |
|             if(stack_.empty()) return false;
 | |
|             return std::any_of(stack_.begin(), stack_.end(),
 | |
|                 [](const context& c) { return c.parent->joinable(); });
 | |
|         }
 | |
| 
 | |
|         const context_list&
 | |
|         stack() const {
 | |
|             return stack_;
 | |
|         }
 | |
| 
 | |
|         /** @brief innermost repeat group */
 | |
|         const group*
 | |
|         innermost_repeat_group() const noexcept {
 | |
|             auto i = std::find_if(stack_.rbegin(), stack_.rend(),
 | |
|                 [](const context& c) { return c.parent->repeatable(); });
 | |
|             return i != stack_.rend() ? i->parent : nullptr;
 | |
|         }
 | |
| 
 | |
|         /** @brief innermost exclusive (alternatives) group */
 | |
|         const group*
 | |
|         innermost_exclusive_group() const noexcept {
 | |
|             auto i = std::find_if(stack_.rbegin(), stack_.rend(),
 | |
|                 [](const context& c) { return c.parent->exclusive(); });
 | |
|             return i != stack_.rend() ? i->parent : nullptr;
 | |
|         }
 | |
| 
 | |
|         /** @brief innermost blocking group */
 | |
|         const group*
 | |
|         innermost_blocking_group() const noexcept {
 | |
|             auto i = std::find_if(stack_.rbegin(), stack_.rend(),
 | |
|                 [](const context& c) { return c.parent->blocking(); });
 | |
|             return i != stack_.rend() ? i->parent : nullptr;
 | |
|         }
 | |
| 
 | |
|         /** @brief returns the outermost group that will be left on next ++*/
 | |
|         const group*
 | |
|         outermost_blocking_group_fully_explored() const noexcept {
 | |
|             if(stack_.empty()) return nullptr;
 | |
| 
 | |
|             const group* g = nullptr;
 | |
|             for(auto i = stack_.rbegin(); i != stack_.rend(); ++i) {
 | |
|                 if(i->cur+1 == i->end) {
 | |
|                     if(i->parent->blocking()) g = i->parent;
 | |
|                 } else {
 | |
|                     return g;
 | |
|                 }
 | |
|             }
 | |
|             return g;
 | |
|         }
 | |
| 
 | |
|         /** @brief outermost join group */
 | |
|         const group*
 | |
|         outermost_join_group() const noexcept {
 | |
|             auto i = std::find_if(stack_.begin(), stack_.end(),
 | |
|                 [](const context& c) { return c.parent->joinable(); });
 | |
|             return i != stack_.end() ? i->parent : nullptr;
 | |
|         }
 | |
| 
 | |
|         const group* root() const noexcept {
 | |
|             return stack_.empty() ? nullptr : stack_.front().parent;
 | |
|         }
 | |
| 
 | |
|         /** @brief common flag prefix of all flags in current group */
 | |
|         arg_string common_flag_prefix() const noexcept {
 | |
|             if(stack_.empty()) return "";
 | |
|             auto g = outermost_join_group();
 | |
|             return g ? g->common_flag_prefix() : arg_string("");
 | |
|         }
 | |
| 
 | |
|         const child&
 | |
|         operator * () const noexcept {
 | |
|             return *stack_.back().cur;
 | |
|         }
 | |
| 
 | |
|         const child*
 | |
|         operator -> () const noexcept {
 | |
|             return &(*stack_.back().cur);
 | |
|         }
 | |
| 
 | |
|         const group&
 | |
|         parent() const noexcept {
 | |
|             return *(stack_.back().parent);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /** @brief go to next element of depth first search */
 | |
|         depth_first_traverser&
 | |
|         operator ++ () {
 | |
|             if(stack_.empty()) return *this;
 | |
|             //at group -> decend into group
 | |
|             if(stack_.back().cur->is_group()) {
 | |
|                 stack_.emplace_back(stack_.back().cur->as_group());
 | |
|             }
 | |
|             else {
 | |
|                 next_sibling();
 | |
|             }
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         /** @brief go to next sibling of current */
 | |
|         depth_first_traverser&
 | |
|         next_sibling() {
 | |
|             if(stack_.empty()) return *this;
 | |
|             ++stack_.back().cur;
 | |
|             //at the end of current group?
 | |
|             while(stack_.back().cur == stack_.back().end) {
 | |
|                 //go to parent
 | |
|                 stack_.pop_back();
 | |
|                 if(stack_.empty()) return *this;
 | |
|                 //go to next sibling in parent
 | |
|                 ++stack_.back().cur;
 | |
|             }
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         /** @brief go to next position after siblings of current */
 | |
|         depth_first_traverser&
 | |
|         next_after_siblings() {
 | |
|             if(stack_.empty()) return *this;
 | |
|             stack_.back().cur = stack_.back().end-1;
 | |
|             next_sibling();
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * @brief
 | |
|          */
 | |
|         depth_first_traverser&
 | |
|         back_to_ancestor(const group* g) {
 | |
|             if(!g) return *this;
 | |
|             while(!stack_.empty()) {
 | |
|                 const auto& top = stack_.back().cur;
 | |
|                 if(top->is_group() && &(top->as_group()) == g) return *this;
 | |
|                 stack_.pop_back();
 | |
|             }
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         /** @brief don't visit next siblings, go back to parent on next ++
 | |
|          *         note: renders siblings unreachable for *this
 | |
|          **/
 | |
|         depth_first_traverser&
 | |
|         skip_siblings() {
 | |
|             if(stack_.empty()) return *this;
 | |
|             //future increments won't visit subsequent siblings:
 | |
|             stack_.back().end = stack_.back().cur+1;
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         /** @brief skips all other alternatives in surrounding exclusive groups
 | |
|          *         on next ++
 | |
|          *         note: renders alternatives unreachable for *this
 | |
|         */
 | |
|         depth_first_traverser&
 | |
|         skip_alternatives() {
 | |
|             if(stack_.empty()) return *this;
 | |
| 
 | |
|             //exclude all other alternatives in surrounding groups
 | |
|             //by making their current position the last one
 | |
|             for(auto& c : stack_) {
 | |
|                 if(c.parent && c.parent->exclusive() && c.cur < c.end)
 | |
|                     c.end = c.cur+1;
 | |
|             }
 | |
| 
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         void invalidate() {
 | |
|             stack_.clear();
 | |
|         }
 | |
| 
 | |
|         inline friend bool operator == (const depth_first_traverser& a,
 | |
|                                         const depth_first_traverser& b)
 | |
|         {
 | |
|             if(a.stack_.empty() || b.stack_.empty()) return false;
 | |
| 
 | |
|             //parents not the same -> different position
 | |
|             if(a.stack_.back().parent != b.stack_.back().parent) return false;
 | |
| 
 | |
|             bool aEnd = a.stack_.back().cur == a.stack_.back().end;
 | |
|             bool bEnd = b.stack_.back().cur == b.stack_.back().end;
 | |
|             //either both at the end of the same parent => same position
 | |
|             if(aEnd && bEnd) return true;
 | |
|             //or only one at the end => not at the same position
 | |
|             if(aEnd || bEnd) return false;
 | |
|             return std::addressof(*a.stack_.back().cur) ==
 | |
|                    std::addressof(*b.stack_.back().cur);
 | |
|         }
 | |
|         inline friend bool operator != (const depth_first_traverser& a,
 | |
|                                         const depth_first_traverser& b)
 | |
|         {
 | |
|             return !(a == b);
 | |
|         }
 | |
| 
 | |
|         memento
 | |
|         undo_point() const {
 | |
|             memento m;
 | |
|             m.level_ = int(stack_.size());
 | |
|             if(!stack_.empty()) m.context_ = stack_.back();
 | |
|             return m;
 | |
|         }
 | |
| 
 | |
|         void undo(const memento& m) {
 | |
|             if(m.level_ < 1) return;
 | |
|             if(m.level_ <= int(stack_.size())) {
 | |
|                 stack_.erase(stack_.begin() + m.level_, stack_.end());
 | |
|                 stack_.back() = m.context_;
 | |
|             }
 | |
|             else if(stack_.empty() && m.level_ == 1) {
 | |
|                 stack_.push_back(m.context_);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         context_list stack_;
 | |
|     };
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     group() = default;
 | |
| 
 | |
|     template<class Param, class... Params>
 | |
|     explicit
 | |
|     group(doc_string docstr, Param param, Params... params):
 | |
|         children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
 | |
|     {
 | |
|         doc(std::move(docstr));
 | |
|         push_back(std::move(param), std::move(params)...);
 | |
|     }
 | |
| 
 | |
|     template<class... Params>
 | |
|     explicit
 | |
|     group(parameter param, Params... params):
 | |
|         children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
 | |
|     {
 | |
|         push_back(std::move(param), std::move(params)...);
 | |
|     }
 | |
| 
 | |
|     template<class P2, class... Ps>
 | |
|     explicit
 | |
|     group(group p1, P2 p2, Ps... ps):
 | |
|         children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
 | |
|     {
 | |
|         push_back(std::move(p1), std::move(p2), std::move(ps)...);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     group(const group&) = default;
 | |
|     group(group&&) = default;
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     group& operator = (const group&) = default;
 | |
|     group& operator = (group&&) = default;
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief determines if a command line argument can be matched by a
 | |
|      *         combination of (partial) matches through any number of children
 | |
|      */
 | |
|     group& joinable(bool yes) {
 | |
|         joinable_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     /** @brief returns if a command line argument can be matched by a
 | |
|      *         combination of (partial) matches through any number of children
 | |
|      */
 | |
|     bool joinable() const noexcept {
 | |
|         return joinable_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief turns explicit scoping on or off
 | |
|      *         operators , & | and other combinating functions will
 | |
|      *         not merge groups that are marked as scoped
 | |
|      */
 | |
|     group& scoped(bool yes) {
 | |
|         scoped_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     /** @brief returns true if operators , & | and other combinating functions
 | |
|      *         will merge groups and false otherwise
 | |
|      */
 | |
|     bool scoped() const noexcept
 | |
|     {
 | |
|         return scoped_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief determines if children are mutually exclusive alternatives */
 | |
|     group& exclusive(bool yes) {
 | |
|         exclusive_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
|     /** @brief returns if children are mutually exclusive alternatives */
 | |
|     bool exclusive() const noexcept {
 | |
|         return exclusive_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns true, if any child is required to match */
 | |
|     bool any_required() const
 | |
|     {
 | |
|         return std::any_of(children_.begin(), children_.end(),
 | |
|             [](const child& n){ return n.required(); });
 | |
|     }
 | |
|     /** @brief returns true, if all children are required to match */
 | |
|     bool all_required() const
 | |
|     {
 | |
|         return std::all_of(children_.begin(), children_.end(),
 | |
|             [](const child& n){ return n.required(); });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns true if any child is optional (=non-required) */
 | |
|     bool any_optional() const {
 | |
|         return !all_required();
 | |
|     }
 | |
|     /** @brief returns true if all children are optional (=non-required) */
 | |
|     bool all_optional() const {
 | |
|         return !any_required();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns if the entire group is blocking / positional */
 | |
|     bool blocking() const noexcept {
 | |
|         return token<group>::blocking() || (exclusive() && all_blocking());
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief determines if the entire group is blocking / positional */
 | |
|     group& blocking(bool yes) {
 | |
|         return token<group>::blocking(yes);
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns true if any child is blocking */
 | |
|     bool any_blocking() const
 | |
|     {
 | |
|         return std::any_of(children_.begin(), children_.end(),
 | |
|             [](const child& n){ return n.blocking(); });
 | |
|     }
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns true if all children is blocking */
 | |
|     bool all_blocking() const
 | |
|     {
 | |
|         return std::all_of(children_.begin(), children_.end(),
 | |
|             [](const child& n){ return n.blocking(); });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns if any child is a value parameter (recursive) */
 | |
|     bool any_flagless() const
 | |
|     {
 | |
|         return std::any_of(children_.begin(), children_.end(),
 | |
|             [](const child& p){
 | |
|                 return p.is_param() && p.as_param().flags().empty();
 | |
|             });
 | |
|     }
 | |
|     /** @brief returns if all children are value parameters (recursive) */
 | |
|     bool all_flagless() const
 | |
|     {
 | |
|         return std::all_of(children_.begin(), children_.end(),
 | |
|             [](const child& p){
 | |
|                 return p.is_param() && p.as_param().flags().empty();
 | |
|             });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds child parameter at the end */
 | |
|     group&
 | |
|     push_back(const parameter& v) {
 | |
|         children_.emplace_back(v);
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds child parameter at the end */
 | |
|     group&
 | |
|     push_back(parameter&& v) {
 | |
|         children_.emplace_back(std::move(v));
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds child group at the end */
 | |
|     group&
 | |
|     push_back(const group& g) {
 | |
|         children_.emplace_back(g);
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds child group at the end */
 | |
|     group&
 | |
|     push_back(group&& g) {
 | |
|         children_.emplace_back(std::move(g));
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds children (groups and/or parameters) */
 | |
|     template<class Param1, class Param2, class... Params>
 | |
|     group&
 | |
|     push_back(Param1&& param1, Param2&& param2, Params&&... params)
 | |
|     {
 | |
|         children_.reserve(children_.size() + 2 + sizeof...(params));
 | |
|         push_back(std::forward<Param1>(param1));
 | |
|         push_back(std::forward<Param2>(param2), std::forward<Params>(params)...);
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds child parameter at the beginning */
 | |
|     group&
 | |
|     push_front(const parameter& v) {
 | |
|         children_.emplace(children_.begin(), v);
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds child parameter at the beginning */
 | |
|     group&
 | |
|     push_front(parameter&& v) {
 | |
|         children_.emplace(children_.begin(), std::move(v));
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds child group at the beginning */
 | |
|     group&
 | |
|     push_front(const group& g) {
 | |
|         children_.emplace(children_.begin(), g);
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds child group at the beginning */
 | |
|     group&
 | |
|     push_front(group&& g) {
 | |
|         children_.emplace(children_.begin(), std::move(g));
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief adds all children of other group at the end */
 | |
|     group&
 | |
|     merge(group&& g)
 | |
|     {
 | |
|         children_.insert(children_.end(),
 | |
|                       std::make_move_iterator(g.begin()),
 | |
|                       std::make_move_iterator(g.end()));
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief adds all children of several other groups at the end */
 | |
|     template<class... Groups>
 | |
|     group&
 | |
|     merge(group&& g1, group&& g2, Groups&&... gs)
 | |
|     {
 | |
|         merge(std::move(g1));
 | |
|         merge(std::move(g2), std::forward<Groups>(gs)...);
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief indexed, nutable access to child */
 | |
|     child& operator [] (size_type index) noexcept {
 | |
|         return children_[index];
 | |
|     }
 | |
|     /** @brief indexed, non-nutable access to child */
 | |
|     const child& operator [] (size_type index) const noexcept {
 | |
|         return children_[index];
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief mutable access to first child */
 | |
|           child& front()       noexcept { return children_.front(); }
 | |
|     /** @brief non-mutable access to first child */
 | |
|     const child& front() const noexcept { return children_.front(); }
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief mutable access to last child */
 | |
|           child& back()       noexcept { return children_.back(); }
 | |
|     /** @brief non-mutable access to last child */
 | |
|     const child& back() const noexcept { return children_.back(); }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns true, if group has no children, false otherwise */
 | |
|     bool empty() const noexcept     { return children_.empty(); }
 | |
| 
 | |
|     /** @brief returns number of children */
 | |
|     size_type size() const noexcept { return children_.size(); }
 | |
| 
 | |
|     /** @brief returns number of nested levels; 1 for a flat group */
 | |
|     size_type depth() const {
 | |
|         size_type n = 0;
 | |
|         for(const auto& c : children_) {
 | |
|             auto l = 1 + c.depth();
 | |
|             if(l > n) n = l;
 | |
|         }
 | |
|         return n;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns mutating iterator to position of first element */
 | |
|           iterator  begin()       noexcept { return children_.begin(); }
 | |
|     /** @brief returns non-mutating iterator to position of first element */
 | |
|     const_iterator  begin() const noexcept { return children_.begin(); }
 | |
|     /** @brief returns non-mutating iterator to position of first element */
 | |
|     const_iterator cbegin() const noexcept { return children_.begin(); }
 | |
| 
 | |
|     /** @brief returns mutating iterator to position one past the last element */
 | |
|           iterator  end()         noexcept { return children_.end(); }
 | |
|     /** @brief returns non-mutating iterator to position one past the last element */
 | |
|     const_iterator  end()   const noexcept { return children_.end(); }
 | |
|     /** @brief returns non-mutating iterator to position one past the last element */
 | |
|     const_iterator cend()   const noexcept { return children_.end(); }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns augmented iterator for depth first searches
 | |
|      *  @details traverser knows end of iteration and can skip over children
 | |
|      */
 | |
|     depth_first_traverser
 | |
|     begin_dfs() const noexcept {
 | |
|         return depth_first_traverser{*this};
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns recursive parameter count */
 | |
|     size_type param_count() const {
 | |
|         size_type c = 0;
 | |
|         for(const auto& n : children_) {
 | |
|             c += n.param_count();
 | |
|         }
 | |
|         return c;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns range of all flags (recursive) */
 | |
|     arg_list all_flags() const
 | |
|     {
 | |
|         std::vector<arg_string> all;
 | |
|         gather_flags(children_, all);
 | |
|         return all;
 | |
|     }
 | |
| 
 | |
|     /** @brief returns true, if no flag occurs as true
 | |
|      *         prefix of any other flag (identical flags will be ignored) */
 | |
|     bool flags_are_prefix_free() const
 | |
|     {
 | |
|         const auto fs = all_flags();
 | |
| 
 | |
|         using std::begin; using std::end;
 | |
|         for(auto i = begin(fs), e = end(fs); i != e; ++i) {
 | |
|             if(!i->empty()) {
 | |
|                 for(auto j = i+1; j != e; ++j) {
 | |
|                     if(!j->empty() && *i != *j) {
 | |
|                         if(i->find(*j) == 0) return false;
 | |
|                         if(j->find(*i) == 0) return false;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns longest common prefix of all flags */
 | |
|     arg_string common_flag_prefix() const
 | |
|     {
 | |
|         arg_list prefixes;
 | |
|         gather_prefixes(children_, prefixes);
 | |
|         return str::longest_common_prefix(prefixes);
 | |
|     }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     //---------------------------------------------------------------
 | |
|     static void
 | |
|     gather_flags(const children_store& nodes, arg_list& all)
 | |
|     {
 | |
|         for(const auto& p : nodes) {
 | |
|             if(p.is_group()) {
 | |
|                 gather_flags(p.as_group().children_, all);
 | |
|             }
 | |
|             else {
 | |
|                 const auto& pf = p.as_param().flags();
 | |
|                 using std::begin;
 | |
|                 using std::end;
 | |
|                 if(!pf.empty()) all.insert(end(all), begin(pf), end(pf));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     //---------------------------------------------------------------
 | |
|     static void
 | |
|     gather_prefixes(const children_store& nodes, arg_list& all)
 | |
|     {
 | |
|         for(const auto& p : nodes) {
 | |
|             if(p.is_group()) {
 | |
|                 gather_prefixes(p.as_group().children_, all);
 | |
|             }
 | |
|             else if(!p.as_param().flags().empty()) {
 | |
|                 auto pfx = str::longest_common_prefix(p.as_param().flags());
 | |
|                 if(!pfx.empty()) all.push_back(std::move(pfx));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     children_store children_;
 | |
|     bool exclusive_ = false;
 | |
|     bool joinable_ = false;
 | |
|     bool scoped_ = false;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief group or parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| using pattern = group::child;
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief apply an action to all parameters in a group
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Action>
 | |
| void for_all_params(group& g, Action&& action)
 | |
| {
 | |
|     for(auto& p : g) {
 | |
|         if(p.is_group()) {
 | |
|             for_all_params(p.as_group(), action);
 | |
|         }
 | |
|         else {
 | |
|             action(p.as_param());
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| template<class Action>
 | |
| void for_all_params(const group& g, Action&& action)
 | |
| {
 | |
|     for(auto& p : g) {
 | |
|         if(p.is_group()) {
 | |
|             for_all_params(p.as_group(), action);
 | |
|         }
 | |
|         else {
 | |
|             action(p.as_param());
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a group of parameters and/or groups
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline group
 | |
| operator , (parameter a, parameter b)
 | |
| {
 | |
|     return group{std::move(a), std::move(b)}.scoped(false);
 | |
| }
 | |
| 
 | |
| //---------------------------------------------------------
 | |
| inline group
 | |
| operator , (parameter a, group b)
 | |
| {
 | |
|     return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable()
 | |
|         && !b.joinable() && (b.doc().empty() || b.doc() == a.doc())
 | |
|        ? b.push_front(std::move(a))
 | |
|        : group{std::move(a), std::move(b)}.scoped(false);
 | |
| }
 | |
| 
 | |
| //---------------------------------------------------------
 | |
| inline group
 | |
| operator , (group a, parameter b)
 | |
| {
 | |
|     return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
 | |
|         && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
 | |
|        ? a.push_back(std::move(b))
 | |
|        : group{std::move(a), std::move(b)}.scoped(false);
 | |
| }
 | |
| 
 | |
| //---------------------------------------------------------
 | |
| inline group
 | |
| operator , (group a, group b)
 | |
| {
 | |
|     return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
 | |
|         && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
 | |
|        ? a.push_back(std::move(b))
 | |
|        : group{std::move(a), std::move(b)}.scoped(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a group of alternative parameters or groups
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| one_of(Param param, Params... params)
 | |
| {
 | |
|     return group{std::move(param), std::move(params)...}.exclusive(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a group of alternative parameters or groups
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline group
 | |
| operator | (parameter a, parameter b)
 | |
| {
 | |
|     return group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
 | |
| }
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| inline group
 | |
| operator | (parameter a, group b)
 | |
| {
 | |
|     return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable()
 | |
|         && !b.joinable()
 | |
|         && (b.doc().empty() || b.doc() == a.doc())
 | |
|         ? b.push_front(std::move(a))
 | |
|         : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
 | |
| }
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| inline group
 | |
| operator | (group a, parameter b)
 | |
| {
 | |
|     return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable()
 | |
|         && a.blocking() == b.blocking()
 | |
|         && (a.doc().empty() || a.doc() == b.doc())
 | |
|         ? a.push_back(std::move(b))
 | |
|         : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
 | |
| }
 | |
| 
 | |
| inline group
 | |
| operator | (group a, group b)
 | |
| {
 | |
|     return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable()
 | |
|         && a.blocking() == b.blocking()
 | |
|         && (a.doc().empty() || a.doc() == b.doc())
 | |
|         ? a.push_back(std::move(b))
 | |
|         : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
 | |
|  *        no interface guarantees; might be changed or removed in the future
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace detail {
 | |
| 
 | |
| inline void set_blocking(bool) {}
 | |
| 
 | |
| template<class P, class... Ps>
 | |
| void set_blocking(bool yes, P& p, Ps&... ps) {
 | |
|     p.blocking(yes);
 | |
|     set_blocking(yes, ps...);
 | |
| }
 | |
| 
 | |
| } // namespace detail
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a parameter/group sequence by making all input objects blocking
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| in_sequence(Param param, Params... params)
 | |
| {
 | |
|     detail::set_blocking(true, param, params...);
 | |
|     return group{std::move(param), std::move(params)...}.scoped(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a parameter/group sequence by making all input objects blocking
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline group
 | |
| operator & (parameter a, parameter b)
 | |
| {
 | |
|     a.blocking(true);
 | |
|     b.blocking(true);
 | |
|     return group{std::move(a), std::move(b)}.scoped(true);
 | |
| }
 | |
| 
 | |
| //---------------------------------------------------------
 | |
| inline group
 | |
| operator & (parameter a, group b)
 | |
| {
 | |
|     a.blocking(true);
 | |
|     return group{std::move(a), std::move(b)}.scoped(true);
 | |
| }
 | |
| 
 | |
| //---------------------------------------------------------
 | |
| inline group
 | |
| operator & (group a, parameter b)
 | |
| {
 | |
|     b.blocking(true);
 | |
|     if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable()
 | |
|         && (a.doc().empty() || a.doc() == b.doc()))
 | |
|     {
 | |
|         return a.push_back(std::move(b));
 | |
|     }
 | |
|     else {
 | |
|         if(!a.all_blocking()) a.blocking(true);
 | |
|         return group{std::move(a), std::move(b)}.scoped(true);
 | |
|     }
 | |
| }
 | |
| 
 | |
| inline group
 | |
| operator & (group a, group b)
 | |
| {
 | |
|     if(!b.all_blocking()) b.blocking(true);
 | |
|     if(a.all_blocking() && !a.exclusive() && !a.repeatable()
 | |
|         && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()))
 | |
|     {
 | |
|         return a.push_back(std::move(b));
 | |
|     }
 | |
|     else {
 | |
|         if(!a.all_blocking()) a.blocking(true);
 | |
|         return group{std::move(a), std::move(b)}.scoped(true);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a group of parameters and/or groups
 | |
|  *        where all single char flag params ("-a", "b", ...) are joinable
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline group
 | |
| joinable(group g) {
 | |
|     return g.joinable(true);
 | |
| }
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| template<class... Params>
 | |
| inline group
 | |
| joinable(parameter param, Params... params)
 | |
| {
 | |
|     return group{std::move(param), std::move(params)...}.joinable(true);
 | |
| }
 | |
| 
 | |
| template<class P2, class... Ps>
 | |
| inline group
 | |
| joinable(group p1, P2 p2, Ps... ps)
 | |
| {
 | |
|     return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true);
 | |
| }
 | |
| 
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| joinable(doc_string docstr, Param param, Params... params)
 | |
| {
 | |
|     return group{std::move(param), std::move(params)...}
 | |
|                 .joinable(true).doc(std::move(docstr));
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a repeatable copy of a parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parameter
 | |
| repeatable(parameter p) {
 | |
|     return p.repeatable(true);
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a repeatable copy of a group
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline group
 | |
| repeatable(group g) {
 | |
|     return g.repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a group of parameters and/or groups
 | |
|  *        that is repeatable as a whole
 | |
|  *        Note that a repeatable group consisting entirely of non-blocking
 | |
|  *        children is equivalent to a non-repeatable group of
 | |
|  *        repeatable children.
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class P2, class... Ps>
 | |
| inline group
 | |
| repeatable(parameter p1, P2 p2, Ps... ps)
 | |
| {
 | |
|     return group{std::move(p1), std::move(p2),
 | |
|                  std::move(ps)...}.repeatable(true);
 | |
| }
 | |
| 
 | |
| template<class P2, class... Ps>
 | |
| inline group
 | |
| repeatable(group p1, P2 p2, Ps... ps)
 | |
| {
 | |
|     return group{std::move(p1), std::move(p2),
 | |
|                  std::move(ps)...}.repeatable(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief makes a parameter greedy (match with top priority)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parameter
 | |
| greedy(parameter p) {
 | |
|     return p.greedy(true);
 | |
| }
 | |
| 
 | |
| inline parameter
 | |
| operator ! (parameter p) {
 | |
|     return greedy(p);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief recursively prepends a prefix to all flags
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parameter&&
 | |
| with_prefix(const arg_string& prefix, parameter&& p) {
 | |
|     return std::move(with_prefix(prefix, p));
 | |
| }
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| inline group&
 | |
| with_prefix(const arg_string& prefix, group& g)
 | |
| {
 | |
|     for(auto& p : g) {
 | |
|         if(p.is_group()) {
 | |
|             with_prefix(prefix, p.as_group());
 | |
|         } else {
 | |
|             with_prefix(prefix, p.as_param());
 | |
|         }
 | |
|     }
 | |
|     return g;
 | |
| }
 | |
| 
 | |
| 
 | |
| inline group&&
 | |
| with_prefix(const arg_string& prefix, group&& params)
 | |
| {
 | |
|     return std::move(with_prefix(prefix, params));
 | |
| }
 | |
| 
 | |
| 
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| with_prefix(arg_string prefix, Param&& param, Params&&... params)
 | |
| {
 | |
|     return with_prefix(prefix, group{std::forward<Param>(param),
 | |
|                                      std::forward<Params>(params)...});
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief recursively prepends a prefix to all flags
 | |
|  *
 | |
|  * @param shortpfx : used for single-letter flags
 | |
|  * @param longpfx  : used for flags with length > 1
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parameter&&
 | |
| with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx,
 | |
|                          parameter&& p)
 | |
| {
 | |
|     return std::move(with_prefixes_short_long(shortpfx, longpfx, p));
 | |
| }
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| inline group&
 | |
| with_prefixes_short_long(const arg_string& shortFlagPrefix,
 | |
|                          const arg_string& longFlagPrefix,
 | |
|                          group& g)
 | |
| {
 | |
|     for(auto& p : g) {
 | |
|         if(p.is_group()) {
 | |
|             with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group());
 | |
|         } else {
 | |
|             with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param());
 | |
|         }
 | |
|     }
 | |
|     return g;
 | |
| }
 | |
| 
 | |
| 
 | |
| inline group&&
 | |
| with_prefixes_short_long(const arg_string& shortFlagPrefix,
 | |
|                          const arg_string& longFlagPrefix,
 | |
|                          group&& params)
 | |
| {
 | |
|     return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
 | |
|                                               params));
 | |
| }
 | |
| 
 | |
| 
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| with_prefixes_short_long(const arg_string& shortFlagPrefix,
 | |
|                          const arg_string& longFlagPrefix,
 | |
|                          Param&& param, Params&&... params)
 | |
| {
 | |
|     return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
 | |
|                                     group{std::forward<Param>(param),
 | |
|                                           std::forward<Params>(params)...});
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief recursively prepends a suffix to all flags
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parameter&&
 | |
| with_suffix(const arg_string& suffix, parameter&& p) {
 | |
|     return std::move(with_suffix(suffix, p));
 | |
| }
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| inline group&
 | |
| with_suffix(const arg_string& suffix, group& g)
 | |
| {
 | |
|     for(auto& p : g) {
 | |
|         if(p.is_group()) {
 | |
|             with_suffix(suffix, p.as_group());
 | |
|         } else {
 | |
|             with_suffix(suffix, p.as_param());
 | |
|         }
 | |
|     }
 | |
|     return g;
 | |
| }
 | |
| 
 | |
| 
 | |
| inline group&&
 | |
| with_suffix(const arg_string& suffix, group&& params)
 | |
| {
 | |
|     return std::move(with_suffix(suffix, params));
 | |
| }
 | |
| 
 | |
| 
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| with_suffix(arg_string suffix, Param&& param, Params&&... params)
 | |
| {
 | |
|     return with_suffix(suffix, group{std::forward<Param>(param),
 | |
|                                      std::forward<Params>(params)...});
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief recursively prepends a suffix to all flags
 | |
|  *
 | |
|  * @param shortsfx : used for single-letter flags
 | |
|  * @param longsfx  : used for flags with length > 1
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parameter&&
 | |
| with_suffixes_short_long(const arg_string& shortsfx, const arg_string& longsfx,
 | |
|                          parameter&& p)
 | |
| {
 | |
|     return std::move(with_suffixes_short_long(shortsfx, longsfx, p));
 | |
| }
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| inline group&
 | |
| with_suffixes_short_long(const arg_string& shortFlagSuffix,
 | |
|                          const arg_string& longFlagSuffix,
 | |
|                          group& g)
 | |
| {
 | |
|     for(auto& p : g) {
 | |
|         if(p.is_group()) {
 | |
|             with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_group());
 | |
|         } else {
 | |
|             with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_param());
 | |
|         }
 | |
|     }
 | |
|     return g;
 | |
| }
 | |
| 
 | |
| 
 | |
| inline group&&
 | |
| with_suffixes_short_long(const arg_string& shortFlagSuffix,
 | |
|                          const arg_string& longFlagSuffix,
 | |
|                          group&& params)
 | |
| {
 | |
|     return std::move(with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
 | |
|                                               params));
 | |
| }
 | |
| 
 | |
| 
 | |
| template<class Param, class... Params>
 | |
| inline group
 | |
| with_suffixes_short_long(const arg_string& shortFlagSuffix,
 | |
|                          const arg_string& longFlagSuffix,
 | |
|                          Param&& param, Params&&... params)
 | |
| {
 | |
|     return with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
 | |
|                                     group{std::forward<Param>(param),
 | |
|                                           std::forward<Params>(params)...});
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parsing implementation details
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| 
 | |
| namespace detail {
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief DFS traverser that keeps track of 'scopes'
 | |
|  *        scope = all parameters that are either bounded by
 | |
|  *        two blocking parameters on the same depth level
 | |
|  *        or the beginning/end of the outermost group
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class scoped_dfs_traverser
 | |
| {
 | |
| public:
 | |
|     using dfs_traverser = group::depth_first_traverser;
 | |
| 
 | |
|     scoped_dfs_traverser() = default;
 | |
| 
 | |
|     explicit
 | |
|     scoped_dfs_traverser(const group& g):
 | |
|         pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{},
 | |
|         ignoreBlocks_{false},
 | |
|         repeatGroupStarted_{false}, repeatGroupContinues_{false}
 | |
|     {}
 | |
| 
 | |
|     const dfs_traverser& base() const noexcept { return pos_; }
 | |
|     const dfs_traverser& last_match() const noexcept { return lastMatch_; }
 | |
| 
 | |
|     const group& parent() const noexcept { return pos_.parent(); }
 | |
| 
 | |
|     const group* innermost_repeat_group() const noexcept {
 | |
|         return pos_.innermost_repeat_group();
 | |
|     }
 | |
|     const group* outermost_join_group() const noexcept {
 | |
|         return pos_.outermost_join_group();
 | |
|     }
 | |
|     const group* innermost_blocking_group() const noexcept {
 | |
|         return pos_.innermost_blocking_group();
 | |
|     }
 | |
|     const group* innermost_exclusive_group() const noexcept {
 | |
|         return pos_.innermost_exclusive_group();
 | |
|     }
 | |
| 
 | |
|     const pattern* operator ->() const noexcept { return pos_.operator->(); }
 | |
|     const pattern& operator *() const noexcept { return *pos_; }
 | |
| 
 | |
|     const pattern* ptr() const noexcept { return pos_.operator->(); }
 | |
| 
 | |
|     explicit operator bool() const noexcept { return bool(pos_); }
 | |
| 
 | |
|     bool joinable() const noexcept { return pos_.joinable(); }
 | |
|     arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); }
 | |
| 
 | |
|     void ignore_blocking(bool yes) { ignoreBlocks_ = yes; }
 | |
| 
 | |
|     void invalidate() {
 | |
|         pos_.invalidate();
 | |
|     }
 | |
| 
 | |
|     bool matched() const noexcept {
 | |
|         return (pos_ == lastMatch_);
 | |
|     }
 | |
| 
 | |
|     bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     scoped_dfs_traverser&
 | |
|     next_sibling() { pos_.next_sibling(); return *this; }
 | |
| 
 | |
|     scoped_dfs_traverser&
 | |
|     next_after_siblings() { pos_.next_after_siblings(); return *this; }
 | |
| 
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     scoped_dfs_traverser&
 | |
|     operator ++ ()
 | |
|     {
 | |
|         if(!pos_) return *this;
 | |
| 
 | |
|         if(pos_.is_last_in_path()) {
 | |
|             return_to_outermost_scope();
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         //current pattern can block if it didn't match already
 | |
|         if(ignoreBlocks_ || matched()) {
 | |
|             ++pos_;
 | |
|         }
 | |
|         else if(!pos_->is_group()) {
 | |
|             //current group can block if we didn't have any match in it
 | |
|             const group* g = pos_.outermost_blocking_group_fully_explored();
 | |
|             //no match in 'g' before -> skip to after its siblings
 | |
|             if(g && !lastMatch_.is_inside(g)) {
 | |
|                 pos_.back_to_ancestor(g).next_after_siblings();
 | |
|                 if(!pos_) return_to_outermost_scope();
 | |
|             }
 | |
|             else if(pos_->blocking()) {
 | |
|                 if(pos_.parent().exclusive()) {
 | |
|                     pos_.next_sibling();
 | |
|                 } else {
 | |
|                     //no match => skip siblings of blocking param
 | |
|                     pos_.next_after_siblings();
 | |
|                 }
 | |
|                 if(!pos_) return_to_outermost_scope();
 | |
|             } else {
 | |
|                 ++pos_;
 | |
|             }
 | |
|         } else {
 | |
|             ++pos_;
 | |
|         }
 | |
|         check_if_left_scope();
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void next_after_match(scoped_dfs_traverser match)
 | |
|     {
 | |
|         if(!match || ignoreBlocks_) return;
 | |
| 
 | |
|         check_repeat_group_start(match);
 | |
| 
 | |
|         lastMatch_ = match.base();
 | |
| 
 | |
|         // if there is a blocking ancestor -> go back to it
 | |
|         if(!match->blocking()) {
 | |
|             match.pos_.back_to_ancestor(match.innermost_blocking_group());
 | |
|         }
 | |
| 
 | |
|         //if match is not in current position & current position is blocking
 | |
|         //=> current position has to be advanced by one so that it is
 | |
|         //no longer reachable within current scope
 | |
|         //(can happen for repeatable, blocking parameters)
 | |
|         if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling();
 | |
| 
 | |
|         if(match->blocking()) {
 | |
|             if(match.pos_.is_alternative()) {
 | |
|                 //discard other alternatives
 | |
|                 match.pos_.skip_alternatives();
 | |
|             }
 | |
| 
 | |
|             if(is_last_in_current_scope(match.pos_)) {
 | |
|                 //if current param is not repeatable -> back to previous scope
 | |
|                 if(!match->repeatable() && !match->is_group()) {
 | |
|                     pos_ = std::move(match.pos_);
 | |
|                     if(!scopes_.empty()) pos_.undo(scopes_.top());
 | |
|                 }
 | |
|                 else { //stay at match position
 | |
|                     pos_ = std::move(match.pos_);
 | |
|                 }
 | |
|             }
 | |
|             else { //not last in current group
 | |
|                 //if current param is not repeatable, go directly to next
 | |
|                 if(!match->repeatable() && !match->is_group()) {
 | |
|                     ++match.pos_;
 | |
|                 }
 | |
| 
 | |
|                 if(match.pos_.level() > pos_.level()) {
 | |
|                     scopes_.push(pos_.undo_point());
 | |
|                     pos_ = std::move(match.pos_);
 | |
|                 }
 | |
|                 else if(match.pos_.level() < pos_.level()) {
 | |
|                     return_to_level(match.pos_.level());
 | |
|                 }
 | |
|                 else {
 | |
|                     pos_ = std::move(match.pos_);
 | |
|                 }
 | |
|             }
 | |
|             posAfterLastMatch_ = pos_;
 | |
|         }
 | |
|         else {
 | |
|             if(match.pos_.level() < pos_.level()) {
 | |
|                 return_to_level(match.pos_.level());
 | |
|             }
 | |
|             posAfterLastMatch_ = pos_;
 | |
|         }
 | |
|         repeatGroupContinues_ = repeat_group_continues();
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     //-----------------------------------------------------
 | |
|     bool is_last_in_current_scope(const dfs_traverser& pos) const
 | |
|     {
 | |
|         if(scopes_.empty()) return pos.is_last_in_path();
 | |
|         //check if we would leave the current scope on ++
 | |
|         auto p = pos;
 | |
|         ++p;
 | |
|         return p.level() < scopes_.top().level();
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void check_repeat_group_start(const scoped_dfs_traverser& newMatch)
 | |
|     {
 | |
|         const auto newrg = newMatch.innermost_repeat_group();
 | |
|         if(!newrg) {
 | |
|             repeatGroupStarted_ = false;
 | |
|         }
 | |
|         else if(lastMatch_.innermost_repeat_group() != newrg) {
 | |
|             repeatGroupStarted_ = true;
 | |
|         }
 | |
|         else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) {
 | |
|             repeatGroupStarted_ = true;
 | |
|         }
 | |
|         else {
 | |
|             //special case: repeat group is outermost group
 | |
|             //=> we can never really 'leave' and 'reenter' it
 | |
|             //but if the current scope is the first element, then we are
 | |
|             //conceptually at a position 'before' the group
 | |
|             repeatGroupStarted_ = scopes_.empty() || (
 | |
|                     newrg == pos_.root() &&
 | |
|                     scopes_.top().param() == &(*pos_.root()->begin()) );
 | |
|         }
 | |
|         repeatGroupContinues_ = repeatGroupStarted_;
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     bool repeat_group_continues() const
 | |
|     {
 | |
|         if(!repeatGroupContinues_) return false;
 | |
|         const auto curRepGroup = pos_.innermost_repeat_group();
 | |
|         if(!curRepGroup) return false;
 | |
|         if(curRepGroup != lastMatch_.innermost_repeat_group()) return false;
 | |
|         if(!posAfterLastMatch_) return false;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void check_if_left_scope()
 | |
|     {
 | |
|         if(posAfterLastMatch_) {
 | |
|             if(pos_.level() < posAfterLastMatch_.level()) {
 | |
|                 while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) {
 | |
|                     pos_.undo(scopes_.top());
 | |
|                     scopes_.pop();
 | |
|                 }
 | |
|                 posAfterLastMatch_.invalidate();
 | |
|             }
 | |
|         }
 | |
|         while(!scopes_.empty() && scopes_.top().level() > pos_.level()) {
 | |
|             pos_.undo(scopes_.top());
 | |
|             scopes_.pop();
 | |
|         }
 | |
|         repeatGroupContinues_ = repeat_group_continues();
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void return_to_outermost_scope()
 | |
|     {
 | |
|         posAfterLastMatch_.invalidate();
 | |
| 
 | |
|         if(scopes_.empty()) {
 | |
|             pos_.invalidate();
 | |
|             repeatGroupContinues_ = false;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) {
 | |
|             pos_.undo(scopes_.top());
 | |
|             scopes_.pop();
 | |
|         }
 | |
|         while(!scopes_.empty()) scopes_.pop();
 | |
| 
 | |
|         repeatGroupContinues_ = repeat_group_continues();
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void return_to_level(int level)
 | |
|     {
 | |
|         if(pos_.level() <= level) return;
 | |
|         while(!scopes_.empty() && pos_.level() > level) {
 | |
|             pos_.undo(scopes_.top());
 | |
|             scopes_.pop();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     dfs_traverser pos_;
 | |
|     dfs_traverser lastMatch_;
 | |
|     dfs_traverser posAfterLastMatch_;
 | |
|     std::stack<dfs_traverser::memento> scopes_;
 | |
|     bool ignoreBlocks_ = false;
 | |
|     bool repeatGroupStarted_ = false;
 | |
|     bool repeatGroupContinues_ = false;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*****************************************************************************
 | |
|  *
 | |
|  * some parameter property predicates
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| struct select_all {
 | |
|     bool operator () (const parameter&) const noexcept { return true; }
 | |
| };
 | |
| 
 | |
| struct select_flags {
 | |
|     bool operator () (const parameter& p) const noexcept {
 | |
|         return !p.flags().empty();
 | |
|     }
 | |
| };
 | |
| 
 | |
| struct select_values {
 | |
|     bool operator () (const parameter& p) const noexcept {
 | |
|         return p.flags().empty();
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief result of a matching operation
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class match_t {
 | |
| public:
 | |
|     using size_type = arg_string::size_type;
 | |
| 
 | |
|     match_t() = default;
 | |
| 
 | |
|     match_t(arg_string s, scoped_dfs_traverser p):
 | |
|         str_{std::move(s)}, pos_{std::move(p)}
 | |
|     {}
 | |
| 
 | |
|     size_type length() const noexcept { return str_.size(); }
 | |
| 
 | |
|     const arg_string& str() const noexcept { return str_; }
 | |
|     const scoped_dfs_traverser& pos() const noexcept { return pos_; }
 | |
| 
 | |
|     explicit operator bool() const noexcept { return bool(pos_); }
 | |
| 
 | |
| private:
 | |
|     arg_string str_;
 | |
|     scoped_dfs_traverser pos_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief finds the first parameter that matches a given string;
 | |
|  *        candidate parameters are traversed using a scoped DFS traverser
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class ParamSelector>
 | |
| match_t
 | |
| full_match(scoped_dfs_traverser pos, const arg_string& arg,
 | |
|            const ParamSelector& select)
 | |
| {
 | |
|     while(pos) {
 | |
|         if(pos->is_param()) {
 | |
|             const auto& param = pos->as_param();
 | |
|             if(select(param)) {
 | |
|                 const auto match = param.match(arg);
 | |
|                 if(match && match.length() == arg.size()) {
 | |
|                     return match_t{arg, std::move(pos)};
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ++pos;
 | |
|     }
 | |
|     return match_t{};
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief finds the first parameter that matches any (non-empty) prefix
 | |
|  *        of a given string;
 | |
|  *        candidate parameters are traversed using a scoped DFS traverser
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class ParamSelector>
 | |
| match_t
 | |
| longest_prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
 | |
|                      const ParamSelector& select)
 | |
| {
 | |
|     match_t longest;
 | |
| 
 | |
|     while(pos) {
 | |
|         if(pos->is_param()) {
 | |
|             const auto& param = pos->as_param();
 | |
|             if(select(param)) {
 | |
|                 auto match = param.match(arg);
 | |
|                 if(match.prefix()) {
 | |
|                     if(match.length() == arg.size()) {
 | |
|                         return match_t{arg, std::move(pos)};
 | |
|                     }
 | |
|                     else if(match.length() > longest.length()) {
 | |
|                         longest = match_t{arg.substr(match.at(), match.length()), 
 | |
|                                           pos};
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ++pos;
 | |
|     }
 | |
|     return longest;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief finds the first parameter that partially matches a given string;
 | |
|  *        candidate parameters are traversed using a scoped DFS traverser
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class ParamSelector>
 | |
| match_t
 | |
| partial_match(scoped_dfs_traverser pos, const arg_string& arg,
 | |
|               const ParamSelector& select)
 | |
| {
 | |
|     while(pos) {
 | |
|         if(pos->is_param()) {
 | |
|             const auto& param = pos->as_param();
 | |
|             if(select(param)) {
 | |
|                 const auto match = param.match(arg);
 | |
|                 if(match) {
 | |
|                     return match_t{arg.substr(match.at(), match.length()),
 | |
|                                    std::move(pos)};
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         ++pos;
 | |
|     }
 | |
|     return match_t{};
 | |
| }
 | |
| 
 | |
| } //namespace detail
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /***************************************************************//**
 | |
|  *
 | |
|  * @brief default command line arguments parser
 | |
|  *
 | |
|  *******************************************************************/
 | |
| class parser
 | |
| {
 | |
| public:
 | |
|     using dfs_traverser = group::depth_first_traverser;
 | |
|     using scoped_dfs_traverser = detail::scoped_dfs_traverser;
 | |
| 
 | |
| 
 | |
|     /*****************************************************//**
 | |
|      * @brief arg -> parameter mapping
 | |
|      *********************************************************/
 | |
|     class arg_mapping {
 | |
|     public:
 | |
|         friend class parser;
 | |
| 
 | |
|         explicit
 | |
|         arg_mapping(arg_index idx, arg_string s,
 | |
|                     const dfs_traverser& match)
 | |
|         :
 | |
|             index_{idx}, arg_{std::move(s)}, match_{match},
 | |
|             repeat_{0}, startsRepeatGroup_{false},
 | |
|             blocked_{false}, conflict_{false}
 | |
|         {}
 | |
| 
 | |
|         explicit
 | |
|         arg_mapping(arg_index idx, arg_string s) :
 | |
|             index_{idx}, arg_{std::move(s)}, match_{},
 | |
|             repeat_{0}, startsRepeatGroup_{false},
 | |
|             blocked_{false}, conflict_{false}
 | |
|         {}
 | |
| 
 | |
|         arg_index index() const noexcept { return index_; }
 | |
|         const arg_string& arg() const noexcept { return arg_; }
 | |
| 
 | |
|         const parameter* param() const noexcept {
 | |
|             return match_ && match_->is_param()
 | |
|                 ? &(match_->as_param()) : nullptr;
 | |
|         }
 | |
| 
 | |
|         std::size_t repeat() const noexcept { return repeat_; }
 | |
| 
 | |
|         bool blocked() const noexcept { return blocked_; }
 | |
|         bool conflict() const noexcept { return conflict_; }
 | |
| 
 | |
|         bool bad_repeat() const noexcept {
 | |
|             if(!param()) return false;
 | |
|             return repeat_ > 0 && !param()->repeatable()
 | |
|                 && !match_.innermost_repeat_group();
 | |
|         }
 | |
| 
 | |
|         bool any_error() const noexcept {
 | |
|             return !match_ || blocked() || conflict() || bad_repeat();
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         arg_index index_;
 | |
|         arg_string arg_;
 | |
|         dfs_traverser match_;
 | |
|         std::size_t repeat_;
 | |
|         bool startsRepeatGroup_;
 | |
|         bool blocked_;
 | |
|         bool conflict_;
 | |
|     };
 | |
| 
 | |
|     /*****************************************************//**
 | |
|      * @brief references a non-matched, required parameter
 | |
|      *********************************************************/
 | |
|     class missing_event {
 | |
|     public:
 | |
|         explicit
 | |
|         missing_event(const parameter* p, arg_index after):
 | |
|             param_{p}, aftIndex_{after}
 | |
|         {}
 | |
| 
 | |
|         const parameter* param() const noexcept { return param_; }
 | |
| 
 | |
|         arg_index after_index() const noexcept { return aftIndex_; }
 | |
| 
 | |
|     private:
 | |
|         const parameter* param_;
 | |
|         arg_index aftIndex_;
 | |
|     };
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     using missing_events = std::vector<missing_event>;
 | |
|     using arg_mappings   = std::vector<arg_mapping>;
 | |
| 
 | |
| 
 | |
| private:
 | |
|     struct miss_candidate {
 | |
|         miss_candidate(dfs_traverser p, arg_index idx,
 | |
|                        bool firstInRepeatGroup = false):
 | |
|             pos{std::move(p)}, index{idx},
 | |
|             startsRepeatGroup{firstInRepeatGroup}
 | |
|         {}
 | |
| 
 | |
|         dfs_traverser pos;
 | |
|         arg_index index;
 | |
|         bool startsRepeatGroup;
 | |
|     };
 | |
|     using miss_candidates = std::vector<miss_candidate>;
 | |
| 
 | |
| 
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief initializes parser with a command line interface
 | |
|      *  @param offset = argument index offset used for reports
 | |
|      * */
 | |
|     explicit
 | |
|     parser(const group& root, arg_index offset = 0):
 | |
|         root_{&root}, pos_{root},
 | |
|         index_{offset-1}, eaten_{0},
 | |
|         args_{}, missCand_{}, blocked_{false}
 | |
|     {
 | |
|         for_each_potential_miss(dfs_traverser{root},
 | |
|             [this](const dfs_traverser& p){
 | |
|                 missCand_.emplace_back(p, index_);
 | |
|             });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief processes one command line argument */
 | |
|     bool operator() (const arg_string& arg)
 | |
|     {
 | |
|         ++eaten_;
 | |
|         ++index_;
 | |
| 
 | |
|         if(!valid()) return false;
 | |
| 
 | |
|         if(!blocked_ && try_match(arg)) return true;
 | |
| 
 | |
|         if(try_match_blocked(arg)) return false;
 | |
| 
 | |
|         //skipping of blocking & required patterns is not allowed
 | |
|         if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) {
 | |
|             blocked_ = true;
 | |
|         }
 | |
| 
 | |
|         add_nomatch(arg);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief returns range of argument -> parameter mappings */
 | |
|     const arg_mappings& args() const {
 | |
|         return args_;
 | |
|     }
 | |
| 
 | |
|     /** @brief returns list of missing events */
 | |
|     missing_events missed() const {
 | |
|         missing_events misses;
 | |
|         misses.reserve(missCand_.size());
 | |
|         for(auto i = missCand_.begin(); i != missCand_.end(); ++i) {
 | |
|             misses.emplace_back(&(i->pos->as_param()), i->index);
 | |
|         }
 | |
|         return misses;
 | |
|     }
 | |
| 
 | |
|     /** @brief returns number of processed command line arguments */
 | |
|     arg_index parse_count() const noexcept { return eaten_; }
 | |
| 
 | |
|     /** @brief returns false if previously processed command line arguments
 | |
|      *         lead to an invalid / inconsistent parsing result
 | |
|      */
 | |
|     bool valid() const noexcept { return bool(pos_); }
 | |
| 
 | |
|     /** @brief returns false if previously processed command line arguments
 | |
|      *         lead to an invalid / inconsistent parsing result
 | |
|      */
 | |
|     explicit operator bool() const noexcept { return valid(); }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     //---------------------------------------------------------------
 | |
|     using match_t = detail::match_t;
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief try to match argument with unreachable parameter */
 | |
|     bool try_match_blocked(const arg_string& arg)
 | |
|     {
 | |
|         //try to match ahead (using temporary parser)
 | |
|         if(pos_) {
 | |
|             auto ahead = *this;
 | |
|             if(try_match_blocked(std::move(ahead), arg)) return true;
 | |
|         }
 | |
| 
 | |
|         //try to match from the beginning (using temporary parser)
 | |
|         if(root_) {
 | |
|             parser all{*root_, index_+1};
 | |
|             if(try_match_blocked(std::move(all), arg)) return true;
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     bool try_match_blocked(parser&& parse, const arg_string& arg)
 | |
|     {
 | |
|         const auto nold = int(parse.args_.size());
 | |
| 
 | |
|         parse.pos_.ignore_blocking(true);
 | |
| 
 | |
|         if(!parse.try_match(arg)) return false;
 | |
| 
 | |
|         for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) {
 | |
|             args_.push_back(*i);
 | |
|             args_.back().blocked_ = true;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief try to find a parameter/pattern that matches 'arg' */
 | |
|     bool try_match(const arg_string& arg)
 | |
|     {
 | |
|         //match greedy parameters before everything else
 | |
|         if(pos_->is_param() && pos_->blocking() && pos_->as_param().greedy()) {
 | |
|             const auto match = pos_->as_param().match(arg);
 | |
|             if(match && match.length() == arg.size()) {
 | |
|                 add_match(detail::match_t{arg,pos_});
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         //try flags first (alone, joinable or strict sequence)
 | |
|         if(try_match_full(arg, detail::select_flags{})) return true;
 | |
|         if(try_match_joined_flags(arg)) return true;
 | |
|         if(try_match_joined_sequence(arg, detail::select_flags{})) return true;
 | |
|         //try value params (alone or strict sequence)
 | |
|         if(try_match_full(arg, detail::select_values{})) return true;
 | |
|         if(try_match_joined_sequence(arg, detail::select_all{})) return true;
 | |
|         //try joinable params + values in any order
 | |
|         if(try_match_joined_params(arg)) return true;
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /**
 | |
|      * @brief try to match full argument
 | |
|      * @param select : predicate that candidate parameters must satisfy
 | |
|      */
 | |
|     template<class ParamSelector>
 | |
|     bool try_match_full(const arg_string& arg, const ParamSelector& select)
 | |
|     {
 | |
|         auto match = detail::full_match(pos_, arg, select);
 | |
|         if(!match) return false;
 | |
|         add_match(match);
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /**
 | |
|      * @brief try to match argument as blocking sequence of parameters
 | |
|      * @param select : predicate that a parameter matching the prefix of
 | |
|      *                 'arg' must satisfy
 | |
|      */
 | |
|     template<class ParamSelector>
 | |
|     bool try_match_joined_sequence(arg_string arg,
 | |
|                                    const ParamSelector& acceptFirst)
 | |
|     {
 | |
|         auto fstMatch = detail::longest_prefix_match(pos_, arg, acceptFirst);
 | |
| 
 | |
|         if(!fstMatch) return false;
 | |
| 
 | |
|         if(fstMatch.str().size() == arg.size()) {
 | |
|             add_match(fstMatch);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         if(!fstMatch.pos()->blocking()) return false;
 | |
| 
 | |
|         auto pos = fstMatch.pos();
 | |
|         pos.ignore_blocking(true);
 | |
|         const auto parent = &pos.parent();
 | |
|         if(!pos->repeatable()) ++pos;
 | |
| 
 | |
|         arg.erase(0, fstMatch.str().size());
 | |
|         std::vector<match_t> matches { std::move(fstMatch) };
 | |
| 
 | |
|         while(!arg.empty() && pos &&
 | |
|               pos->blocking() && pos->is_param() &&
 | |
|               (&pos.parent() == parent))
 | |
|         {
 | |
|             auto match = pos->as_param().match(arg);
 | |
| 
 | |
|             if(match.prefix()) {
 | |
|                 matches.emplace_back(arg.substr(0,match.length()), pos);
 | |
|                 arg.erase(0, match.length());
 | |
|                 if(!pos->repeatable()) ++pos;
 | |
|             }
 | |
|             else {
 | |
|                 if(!pos->repeatable()) return false;
 | |
|                 ++pos;
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         //if arg not fully covered => discard temporary matches
 | |
|         if(!arg.empty() || matches.empty()) return false;
 | |
| 
 | |
|         for(const auto& m : matches) add_match(m);
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief try to match 'arg' as a concatenation of joinable flags */
 | |
|     bool try_match_joined_flags(const arg_string& arg)
 | |
|     {
 | |
|         return find_join_group(pos_, [&](const group& g) {
 | |
|             return try_match_joined(g, arg, detail::select_flags{},
 | |
|                                     g.common_flag_prefix());
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief try to match 'arg' as a concatenation of joinable parameters */
 | |
|     bool try_match_joined_params(const arg_string& arg)
 | |
|     {
 | |
|         return find_join_group(pos_, [&](const group& g) {
 | |
|             return try_match_joined(g, arg, detail::select_all{});
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief try to match 'arg' as concatenation of joinable parameters
 | |
|      *         that are all contained within one group
 | |
|      */
 | |
|     template<class ParamSelector>
 | |
|     bool try_match_joined(const group& joinGroup, arg_string arg,
 | |
|                           const ParamSelector& select,
 | |
|                           const arg_string& prefix = "")
 | |
|     {
 | |
|         //temporary parser with 'joinGroup' as top-level group
 | |
|         parser parse {joinGroup};
 | |
|         //records temporary matches
 | |
|         std::vector<match_t> matches;
 | |
| 
 | |
|         while(!arg.empty()) {
 | |
|             auto match = detail::longest_prefix_match(parse.pos_, arg, select);
 | |
| 
 | |
|             if(!match) return false;
 | |
| 
 | |
|             arg.erase(0, match.str().size());
 | |
|             //make sure prefix is always present after the first match
 | |
|             //so that, e.g., flags "-a" and "-b" will be found in "-ab"
 | |
|             if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 &&
 | |
|                 prefix != match.str())
 | |
|             {
 | |
|                 arg.insert(0,prefix);
 | |
|             }
 | |
| 
 | |
|             parse.add_match(match);
 | |
|             matches.push_back(std::move(match));
 | |
|         }
 | |
| 
 | |
|         if(!arg.empty() || matches.empty()) return false;
 | |
| 
 | |
|         if(!parse.missCand_.empty()) return false;
 | |
|         for(const auto& a : parse.args_) if(a.any_error()) return false;
 | |
| 
 | |
|         //replay matches onto *this
 | |
|         for(const auto& m : matches) add_match(m);
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     template<class GroupSelector>
 | |
|     bool find_join_group(const scoped_dfs_traverser& start,
 | |
|                          const GroupSelector& accept) const
 | |
|     {
 | |
|         if(start && start.parent().joinable()) {
 | |
|             const auto& g = start.parent();
 | |
|             if(accept(g)) return true;
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         auto pos = start;
 | |
|         while(pos) {
 | |
|             if(pos->is_group() && pos->as_group().joinable()) {
 | |
|                 const auto& g = pos->as_group();
 | |
|                 if(accept(g)) return true;
 | |
|                 pos.next_sibling();
 | |
|             }
 | |
|             else {
 | |
|                 ++pos;
 | |
|             }
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     void add_nomatch(const arg_string& arg) {
 | |
|         args_.emplace_back(index_, arg);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     void add_match(const match_t& match)
 | |
|     {
 | |
|         const auto& pos = match.pos();
 | |
|         if(!pos || !pos->is_param()) return;
 | |
| 
 | |
|         pos_.next_after_match(pos);
 | |
| 
 | |
|         arg_mapping newArg{index_, match.str(), pos.base()};
 | |
|         newArg.repeat_ = occurrences_of(&pos->as_param());
 | |
|         newArg.conflict_ = check_conflicts(pos.base());
 | |
|         newArg.startsRepeatGroup_ = pos_.start_of_repeat_group();
 | |
|         args_.push_back(std::move(newArg));
 | |
| 
 | |
|         add_miss_candidates_after(pos);
 | |
|         clean_miss_candidates_for(pos.base());
 | |
|         discard_alternative_miss_candidates(pos.base());
 | |
| 
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     bool check_conflicts(const dfs_traverser& match)
 | |
|     {
 | |
|         if(pos_.start_of_repeat_group()) return false;
 | |
|         bool conflict = false;
 | |
|         for(const auto& m : match.stack()) {
 | |
|             if(m.parent->exclusive()) {
 | |
|                 for(auto i = args_.rbegin(); i != args_.rend(); ++i) {
 | |
|                     if(!i->blocked()) {
 | |
|                         for(const auto& c : i->match_.stack()) {
 | |
|                             //sibling within same exclusive group => conflict
 | |
|                             if(c.parent == m.parent && c.cur != m.cur) {
 | |
|                                 conflict = true;
 | |
|                                 i->conflict_ = true;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     //check for conflicts only within current repeat cycle
 | |
|                     if(i->startsRepeatGroup_) break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return conflict;
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void clean_miss_candidates_for(const dfs_traverser& match)
 | |
|     {
 | |
|         auto i = std::find_if(missCand_.rbegin(), missCand_.rend(),
 | |
|             [&](const miss_candidate& m) {
 | |
|                 return &(*m.pos) == &(*match);
 | |
|             });
 | |
| 
 | |
|         if(i != missCand_.rend()) {
 | |
|             missCand_.erase(prev(i.base()));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void discard_alternative_miss_candidates(const dfs_traverser& match)
 | |
|     {
 | |
|         if(missCand_.empty()) return;
 | |
|         //find out, if miss candidate is sibling of one of the same
 | |
|         //alternative groups that the current match is a member of
 | |
|         //if so, we can discard the miss
 | |
| 
 | |
|         //go through all exclusive groups of matching pattern
 | |
|         for(const auto& m : match.stack()) {
 | |
|             if(m.parent->exclusive()) {
 | |
|                 for(auto i = int(missCand_.size())-1; i >= 0; --i) {
 | |
|                     bool removed = false;
 | |
|                     for(const auto& c : missCand_[i].pos.stack()) {
 | |
|                         //sibling within same exclusive group => discard
 | |
|                         if(c.parent == m.parent && c.cur != m.cur) {
 | |
|                             missCand_.erase(missCand_.begin() + i);
 | |
|                             if(missCand_.empty()) return;
 | |
|                             removed = true;
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
|                     //remove miss candidates only within current repeat cycle
 | |
|                     if(i > 0 && removed) {
 | |
|                         if(missCand_[i-1].startsRepeatGroup) break;
 | |
|                     } else {
 | |
|                         if(missCand_[i].startsRepeatGroup) break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     void add_miss_candidates_after(const scoped_dfs_traverser& match)
 | |
|     {
 | |
|         auto npos = match.base();
 | |
|         if(npos.is_alternative()) npos.skip_alternatives();
 | |
|         ++npos;
 | |
|         //need to add potential misses if:
 | |
|         //either new repeat group was started
 | |
|         const auto newRepGroup = match.innermost_repeat_group();
 | |
|         if(newRepGroup) {
 | |
|             if(pos_.start_of_repeat_group()) {
 | |
|                 for_each_potential_miss(std::move(npos),
 | |
|                     [&,this](const dfs_traverser& pos) {
 | |
|                         //only add candidates within repeat group
 | |
|                         if(newRepGroup == pos.innermost_repeat_group()) {
 | |
|                             missCand_.emplace_back(pos, index_, true);
 | |
|                         }
 | |
|                     });
 | |
|             }
 | |
|         }
 | |
|         //... or an optional blocking param was hit
 | |
|         else if(match->blocking() && !match->required() &&
 | |
|             npos.level() >= match.base().level())
 | |
|         {
 | |
|             for_each_potential_miss(std::move(npos),
 | |
|                 [&,this](const dfs_traverser& pos) {
 | |
|                     //only add new candidates
 | |
|                     if(std::find_if(missCand_.begin(), missCand_.end(),
 | |
|                         [&](const miss_candidate& c){
 | |
|                             return &(*c.pos) == &(*pos);
 | |
|                         }) == missCand_.end())
 | |
|                     {
 | |
|                         missCand_.emplace_back(pos, index_);
 | |
|                     }
 | |
|                 });
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     template<class Action>
 | |
|     static void
 | |
|     for_each_potential_miss(dfs_traverser pos, Action&& action)
 | |
|     {
 | |
|         const auto level = pos.level();
 | |
|         while(pos && pos.level() >= level) {
 | |
|             if(pos->is_group() ) {
 | |
|                 const auto& g = pos->as_group();
 | |
|                 if(g.all_optional() || (g.exclusive() && g.any_optional())) {
 | |
|                     pos.next_sibling();
 | |
|                 } else {
 | |
|                     ++pos;
 | |
|                 }
 | |
|             } else {  //param
 | |
|                 if(pos->required()) {
 | |
|                     action(pos);
 | |
|                     ++pos;
 | |
|                 } else if(pos->blocking()) { //optional + blocking
 | |
|                     pos.next_after_siblings();
 | |
|                 } else {
 | |
|                     ++pos;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     std::size_t occurrences_of(const parameter* p) const
 | |
|     {
 | |
|         if(!p) return 0;
 | |
| 
 | |
|         auto i = std::find_if(args_.rbegin(), args_.rend(),
 | |
|             [p](const arg_mapping& a){ return a.param() == p; });
 | |
| 
 | |
|         if(i != args_.rend()) return i->repeat() + 1;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     const group* root_;
 | |
|     scoped_dfs_traverser pos_;
 | |
|     arg_index index_;
 | |
|     arg_index eaten_;
 | |
|     arg_mappings args_;
 | |
|     miss_candidates missCand_;
 | |
|     bool blocked_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief contains argument -> parameter mappings
 | |
|  *        and missing parameters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class parsing_result
 | |
| {
 | |
| public:
 | |
|     using arg_mapping    = parser::arg_mapping;
 | |
|     using arg_mappings   = parser::arg_mappings;
 | |
|     using missing_event  = parser::missing_event;
 | |
|     using missing_events = parser::missing_events;
 | |
|     using iterator       = arg_mappings::const_iterator;
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief default: empty result */
 | |
|     parsing_result() = default;
 | |
| 
 | |
|     parsing_result(arg_mappings arg2param, missing_events misses):
 | |
|         arg2param_{std::move(arg2param)}, missing_{std::move(misses)}
 | |
|     {}
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     /** @brief returns number of arguments that could not be mapped to
 | |
|      *         a parameter
 | |
|      */
 | |
|     arg_mappings::size_type
 | |
|     unmapped_args_count() const noexcept {
 | |
|         return std::count_if(arg2param_.begin(), arg2param_.end(),
 | |
|             [](const arg_mapping& a){ return !a.param(); });
 | |
|     }
 | |
| 
 | |
|     /** @brief returns if any argument could only be matched by an
 | |
|      *         unreachable parameter
 | |
|      */
 | |
|     bool any_blocked() const noexcept {
 | |
|         return std::any_of(arg2param_.begin(), arg2param_.end(),
 | |
|             [](const arg_mapping& a){ return a.blocked(); });
 | |
|     }
 | |
| 
 | |
|     /** @brief returns if any argument matched more than one parameter
 | |
|      *         that were mutually exclusive */
 | |
|     bool any_conflict() const noexcept {
 | |
|         return std::any_of(arg2param_.begin(), arg2param_.end(),
 | |
|             [](const arg_mapping& a){ return a.conflict(); });
 | |
|     }
 | |
| 
 | |
|     /** @brief returns if any parameter matched repeatedly although
 | |
|      *         it was not allowed to */
 | |
|     bool any_bad_repeat() const noexcept {
 | |
|         return std::any_of(arg2param_.begin(), arg2param_.end(),
 | |
|             [](const arg_mapping& a){ return a.bad_repeat(); });
 | |
|     }
 | |
| 
 | |
|     /** @brief returns true if any parsing error / violation of the
 | |
|      *         command line interface definition occurred */
 | |
|     bool any_error() const noexcept {
 | |
|         return unmapped_args_count() > 0 || !missing().empty() ||
 | |
|                any_blocked() || any_conflict() || any_bad_repeat();
 | |
|     }
 | |
| 
 | |
|     /** @brief returns true if no parsing error / violation of the
 | |
|      *         command line interface definition occurred */
 | |
|     explicit operator bool() const noexcept { return !any_error(); }
 | |
| 
 | |
|     /** @brief access to range of missing parameter match events */
 | |
|     const missing_events& missing() const noexcept { return missing_; }
 | |
| 
 | |
|     /** @brief returns non-mutating iterator to position of
 | |
|      *         first argument -> parameter mapping  */
 | |
|     iterator begin() const noexcept { return arg2param_.begin(); }
 | |
|     /** @brief returns non-mutating iterator to position one past the
 | |
|      *         last argument -> parameter mapping  */
 | |
|     iterator end()   const noexcept { return arg2param_.end(); }
 | |
| 
 | |
| private:
 | |
|     //-----------------------------------------------------
 | |
|     arg_mappings arg2param_;
 | |
|     missing_events missing_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| namespace detail {
 | |
| namespace {
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief correct some common problems
 | |
|  *        does not - and MUST NOT - change the number of arguments
 | |
|  *        (no insertions or deletions allowed)
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| void sanitize_args(arg_list& args)
 | |
| {
 | |
|     //e.g. {"-o12", ".34"} -> {"-o", "12.34"}
 | |
| 
 | |
|     if(args.empty()) return;
 | |
| 
 | |
|     for(auto i = begin(args)+1; i != end(args); ++i) {
 | |
|         if(i != begin(args) && i->size() > 1 &&
 | |
|             i->find('.') == 0 && std::isdigit((*i)[1]) )
 | |
|         {
 | |
|             //find trailing digits in previous arg
 | |
|             using std::prev;
 | |
|             auto& prv = *prev(i);
 | |
|             auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(),
 | |
|                 [](arg_string::value_type c){
 | |
|                     return std::isdigit(c);
 | |
|                 }).base();
 | |
| 
 | |
|             //handle leading sign
 | |
|             if(fstDigit > prv.begin() &&
 | |
|                 (*prev(fstDigit) == '+' || *prev(fstDigit) == '-'))
 | |
|             {
 | |
|                 --fstDigit;
 | |
|             }
 | |
| 
 | |
|             //prepend digits from previous arg
 | |
|             i->insert(begin(*i), fstDigit, end(prv));
 | |
| 
 | |
|             //erase digits in previous arg
 | |
|             prv.erase(fstDigit, end(prv));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief executes actions based on a parsing result
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| void execute_actions(const parsing_result& res)
 | |
| {
 | |
|     for(const auto& m : res) {
 | |
|         if(m.param()) {
 | |
|             const auto& param = *(m.param());
 | |
| 
 | |
|             if(m.repeat() > 0) param.notify_repeated(m.index());
 | |
|             if(m.blocked())    param.notify_blocked(m.index());
 | |
|             if(m.conflict())   param.notify_conflict(m.index());
 | |
|             //main action
 | |
|             if(!m.any_error()) param.execute_actions(m.arg());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for(auto m : res.missing()) {
 | |
|         if(m.param()) m.param()->notify_missing(m.after_index());
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parses input args
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| static parsing_result
 | |
| parse_args(const arg_list& args, const group& cli,
 | |
|            arg_index offset = 0)
 | |
| {
 | |
|     //parse args and store unrecognized arg indices
 | |
|     parser parse{cli, offset};
 | |
|     for(const auto& arg : args) {
 | |
|         parse(arg);
 | |
|         if(!parse.valid()) break;
 | |
|     }
 | |
| 
 | |
|     return parsing_result{parse.args(), parse.missed()};
 | |
| }
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parses input args & executes actions
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| static parsing_result
 | |
| parse_and_execute(const arg_list& args, const group& cli,
 | |
|                   arg_index offset = 0)
 | |
| {
 | |
|     auto result = parse_args(args, cli, offset);
 | |
| 
 | |
|     execute_actions(result);
 | |
| 
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| } //anonymous namespace
 | |
| } // namespace detail
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parses vector of arg strings and executes actions
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parsing_result
 | |
| parse(arg_list args, const group& cli)
 | |
| {
 | |
|     detail::sanitize_args(args);
 | |
|     return detail::parse_and_execute(args, cli);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parses initializer_list of C-style arg strings and executes actions
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parsing_result
 | |
| parse(std::initializer_list<const char*> arglist, const group& cli)
 | |
| {
 | |
|     arg_list args;
 | |
|     args.reserve(arglist.size());
 | |
|     for(auto a : arglist) {
 | |
|         args.push_back(a);
 | |
|     }
 | |
| 
 | |
|     return parse(std::move(args), cli);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parses range of arg strings and executes actions
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class InputIterator>
 | |
| inline parsing_result
 | |
| parse(InputIterator first, InputIterator last, const group& cli)
 | |
| {
 | |
|     return parse(arg_list(first,last), cli);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief parses the standard array of command line arguments; omits argv[0]
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline parsing_result
 | |
| parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
 | |
| {
 | |
|     arg_list args;
 | |
|     if(offset < argc) args.assign(argv+offset, argv+argc);
 | |
|     detail::sanitize_args(args);
 | |
|     return detail::parse_and_execute(args, cli, offset);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief filter predicate for parameters and groups;
 | |
|  *        Can be used to limit documentation generation to parameter subsets.
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class param_filter
 | |
| {
 | |
| public:
 | |
|     /** @brief only allow parameters with given prefix */
 | |
|     param_filter& prefix(const arg_string& p) noexcept {
 | |
|         prefix_ = p; return *this;
 | |
|     }
 | |
|     /** @brief only allow parameters with given prefix */
 | |
|     param_filter& prefix(arg_string&& p) noexcept {
 | |
|         prefix_ = std::move(p); return *this;
 | |
|     }
 | |
|     const arg_string& prefix()  const noexcept { return prefix_; }
 | |
| 
 | |
|     /** @brief only allow parameters with given requirement status */
 | |
|     param_filter& required(tri t)  noexcept { required_ = t; return *this; }
 | |
|     tri           required() const noexcept { return required_; }
 | |
| 
 | |
|     /** @brief only allow parameters with given blocking status */
 | |
|     param_filter& blocking(tri t)  noexcept { blocking_ = t; return *this; }
 | |
|     tri           blocking() const noexcept { return blocking_; }
 | |
| 
 | |
|     /** @brief only allow parameters with given repeatable status */
 | |
|     param_filter& repeatable(tri t)  noexcept { repeatable_ = t; return *this; }
 | |
|     tri           repeatable() const noexcept { return repeatable_; }
 | |
| 
 | |
|     /** @brief only allow parameters with given docstring status */
 | |
|     param_filter& has_doc(tri t)  noexcept { hasDoc_ = t; return *this; }
 | |
|     tri           has_doc() const noexcept { return hasDoc_; }
 | |
| 
 | |
| 
 | |
|     /** @brief returns true, if parameter satisfies all filters */
 | |
|     bool operator() (const parameter& p) const noexcept {
 | |
|         if(!prefix_.empty()) {
 | |
|             if(!std::any_of(p.flags().begin(), p.flags().end(),
 | |
|                 [&](const arg_string& flag){
 | |
|                     return str::has_prefix(flag, prefix_);
 | |
|                 })) return false;
 | |
|         }
 | |
|         if(required()   != p.required())     return false;
 | |
|         if(blocking()   != p.blocking())     return false;
 | |
|         if(repeatable() != p.repeatable())   return false;
 | |
|         if(has_doc()    != !p.doc().empty()) return false;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     arg_string prefix_;
 | |
|     tri required_   = tri::either;
 | |
|     tri blocking_   = tri::either;
 | |
|     tri repeatable_ = tri::either;
 | |
|     tri hasDoc_     = tri::yes;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief documentation formatting options
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class doc_formatting
 | |
| {
 | |
| public:
 | |
|     using string = doc_string;
 | |
| 
 | |
|     /** @brief same as 'first_column' */
 | |
| #if __cplusplus >= 201402L
 | |
|     [[deprecated]]
 | |
| #endif
 | |
|     doc_formatting& start_column(int col) { return first_column(col); }
 | |
| #if __cplusplus >= 201402L
 | |
|     [[deprecated]]
 | |
| #endif
 | |
|     int start_column() const noexcept { return first_column(); }
 | |
| 
 | |
|     /** @brief determines column where documentation printing starts */
 | |
|     doc_formatting&
 | |
|     first_column(int col) {
 | |
|         //limit to [0,last_column] but push doc_column to the right if necessary
 | |
|         if(col < 0) col = 0;
 | |
|         else if(col > last_column()) col = last_column();
 | |
|         if(col > doc_column()) doc_column(first_column());
 | |
|         firstCol_ = col;
 | |
|         return *this;
 | |
|     }
 | |
|     int first_column() const noexcept {
 | |
|         return firstCol_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines column where docstrings start */
 | |
|     doc_formatting&
 | |
|     doc_column(int col) {
 | |
|         //limit to [first_column,last_column]
 | |
|         if(col < 0) col = 0;
 | |
|         else if(col < first_column()) col = first_column();
 | |
|         else if(col > last_column()) col = last_column();
 | |
|         docCol_ = col;
 | |
|         return *this;
 | |
|     }
 | |
|     int doc_column() const noexcept {
 | |
|         return docCol_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines column that no documentation text must exceed;
 | |
|      *         (text should be wrapped appropriately after this column)
 | |
|      */
 | |
|     doc_formatting&
 | |
|     last_column(int col) {
 | |
|         //limit to [first_column,oo] but push doc_column to the left if necessary
 | |
|         if(col < first_column()) col = first_column();
 | |
|         if(col < doc_column()) doc_column(col);
 | |
|         lastCol_ = col;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     int last_column() const noexcept {
 | |
|         return lastCol_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines indent of documentation lines
 | |
|      *         for children of a documented group */
 | |
|     doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; }
 | |
|     int             indent_size() const noexcept  { return indentSize_; }
 | |
| 
 | |
|     /** @brief determines string to be used
 | |
|      *         if a parameter has no flags and no label  */
 | |
|     doc_formatting& empty_label(const string& label) {
 | |
|         emptyLabel_ = label;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& empty_label() const noexcept { return emptyLabel_; }
 | |
| 
 | |
|     /** @brief determines string for separating parameters */
 | |
|     doc_formatting& param_separator(const string& sep) {
 | |
|         paramSep_ = sep;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& param_separator() const noexcept { return paramSep_; }
 | |
| 
 | |
|     /** @brief determines string for separating groups (in usage lines) */
 | |
|     doc_formatting& group_separator(const string& sep) {
 | |
|         groupSep_ = sep;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& group_separator() const noexcept { return groupSep_; }
 | |
| 
 | |
|     /** @brief determines string for separating alternative parameters */
 | |
|     doc_formatting& alternative_param_separator(const string& sep) {
 | |
|         altParamSep_ = sep;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& alternative_param_separator() const noexcept { return altParamSep_; }
 | |
| 
 | |
|     /** @brief determines string for separating alternative groups */
 | |
|     doc_formatting& alternative_group_separator(const string& sep) {
 | |
|         altGroupSep_ = sep;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& alternative_group_separator() const noexcept { return altGroupSep_; }
 | |
| 
 | |
|     /** @brief determines string for separating flags of the same parameter */
 | |
|     doc_formatting& flag_separator(const string& sep) {
 | |
|         flagSep_ = sep;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& flag_separator() const noexcept { return flagSep_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding parameter labels */
 | |
|     doc_formatting&
 | |
|     surround_labels(const string& prefix, const string& postfix) {
 | |
|         labelPre_ = prefix;
 | |
|         labelPst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& label_prefix()  const noexcept { return labelPre_; }
 | |
|     const string& label_postfix() const noexcept { return labelPst_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding optional parameters/groups */
 | |
|     doc_formatting&
 | |
|     surround_optional(const string& prefix, const string& postfix) {
 | |
|         optionPre_ = prefix;
 | |
|         optionPst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& optional_prefix()  const noexcept { return optionPre_; }
 | |
|     const string& optional_postfix() const noexcept { return optionPst_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding repeatable parameters/groups */
 | |
|     doc_formatting&
 | |
|     surround_repeat(const string& prefix, const string& postfix) {
 | |
|         repeatPre_ = prefix;
 | |
|         repeatPst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& repeat_prefix()  const noexcept { return repeatPre_; }
 | |
|     const string& repeat_postfix() const noexcept { return repeatPst_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding exclusive groups */
 | |
|     doc_formatting&
 | |
|     surround_alternatives(const string& prefix, const string& postfix) {
 | |
|         alternPre_ = prefix;
 | |
|         alternPst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& alternatives_prefix()  const noexcept { return alternPre_; }
 | |
|     const string& alternatives_postfix() const noexcept { return alternPst_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding alternative flags */
 | |
|     doc_formatting&
 | |
|     surround_alternative_flags(const string& prefix, const string& postfix) {
 | |
|         alternFlagPre_ = prefix;
 | |
|         alternFlagPst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& alternative_flags_prefix()  const noexcept { return alternFlagPre_; }
 | |
|     const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding non-exclusive groups */
 | |
|     doc_formatting&
 | |
|     surround_group(const string& prefix, const string& postfix) {
 | |
|         groupPre_ = prefix;
 | |
|         groupPst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& group_prefix()  const noexcept { return groupPre_; }
 | |
|     const string& group_postfix() const noexcept { return groupPst_; }
 | |
| 
 | |
|     /** @brief determines strings surrounding joinable groups */
 | |
|     doc_formatting&
 | |
|     surround_joinable(const string& prefix, const string& postfix) {
 | |
|         joinablePre_ = prefix;
 | |
|         joinablePst_ = postfix;
 | |
|         return *this;
 | |
|     }
 | |
|     const string& joinable_prefix()  const noexcept { return joinablePre_; }
 | |
|     const string& joinable_postfix() const noexcept { return joinablePst_; }
 | |
| 
 | |
|     /** @brief determines maximum number of flags per parameter to be printed
 | |
|      *         in detailed parameter documentation lines */
 | |
|     doc_formatting& max_flags_per_param_in_doc(int max) {
 | |
|         maxAltInDocs_ = max > 0 ? max : 0;
 | |
|         return *this;
 | |
|     }
 | |
|     int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; }
 | |
| 
 | |
|     /** @brief determines maximum number of flags per parameter to be printed
 | |
|      *         in usage lines */
 | |
|     doc_formatting& max_flags_per_param_in_usage(int max) {
 | |
|         maxAltInUsage_ = max > 0 ? max : 0;
 | |
|         return *this;
 | |
|     }
 | |
|     int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; }
 | |
| 
 | |
|     /** @brief determines number of empty rows after one single-line
 | |
|      *         documentation entry */
 | |
|     doc_formatting& line_spacing(int lines) {
 | |
|         lineSpc_ = lines > 0 ? lines : 0;
 | |
|         return *this;
 | |
|     }
 | |
|     int line_spacing() const noexcept { return lineSpc_; }
 | |
| 
 | |
|     /** @brief determines number of empty rows before and after a paragraph;
 | |
|      *         a paragraph is defined by a documented group or if
 | |
|      *         a parameter documentation entry used more than one line */
 | |
|     doc_formatting& paragraph_spacing(int lines) {
 | |
|         paragraphSpc_ = lines > 0 ? lines : 0;
 | |
|         return *this;
 | |
|     }
 | |
|     int paragraph_spacing() const noexcept { return paragraphSpc_; }
 | |
| 
 | |
|     /** @brief determines if alternative flags with a common prefix should
 | |
|      *         be printed in a merged fashion */
 | |
|     doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) {
 | |
|         mergeAltCommonPfx_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
|     bool merge_alternative_flags_with_common_prefix() const noexcept {
 | |
|         return mergeAltCommonPfx_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines if joinable flags with a common prefix should
 | |
|      *         be printed in a merged fashion */
 | |
|     doc_formatting& merge_joinable_with_common_prefix(bool yes = true) {
 | |
|         mergeJoinableCommonPfx_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
|     bool merge_joinable_with_common_prefix() const noexcept {
 | |
|         return mergeJoinableCommonPfx_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines if children of exclusive groups should be printed
 | |
|      *         on individual lines if the exceed 'alternatives_min_split_size'
 | |
|      */
 | |
|     doc_formatting& split_alternatives(bool yes = true) {
 | |
|         splitTopAlt_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
|     bool split_alternatives() const noexcept {
 | |
|         return splitTopAlt_;
 | |
|     }
 | |
| 
 | |
|     /** @brief determines how many children exclusive groups can have before
 | |
|      *         their children are printed on individual usage lines */
 | |
|     doc_formatting& alternatives_min_split_size(int size) {
 | |
|         groupSplitSize_ = size > 0 ? size : 0;
 | |
|         return *this;
 | |
|     }
 | |
|     int alternatives_min_split_size() const noexcept { return groupSplitSize_; }
 | |
| 
 | |
|     /** @brief determines whether to ignore new line characters in docstrings
 | |
|      */
 | |
|     doc_formatting& ignore_newline_chars(bool yes = true) {
 | |
|         ignoreNewlines_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
|     bool ignore_newline_chars() const noexcept {
 | |
|         return ignoreNewlines_;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     string paramSep_      = string(" ");
 | |
|     string groupSep_      = string(" ");
 | |
|     string altParamSep_   = string("|");
 | |
|     string altGroupSep_   = string(" | ");
 | |
|     string flagSep_       = string(", ");
 | |
|     string labelPre_      = string("<");
 | |
|     string labelPst_      = string(">");
 | |
|     string optionPre_     = string("[");
 | |
|     string optionPst_     = string("]");
 | |
|     string repeatPre_     = string("");
 | |
|     string repeatPst_     = string("...");
 | |
|     string groupPre_      = string("(");
 | |
|     string groupPst_      = string(")");
 | |
|     string alternPre_     = string("(");
 | |
|     string alternPst_     = string(")");
 | |
|     string alternFlagPre_ = string("");
 | |
|     string alternFlagPst_ = string("");
 | |
|     string joinablePre_   = string("(");
 | |
|     string joinablePst_   = string(")");
 | |
|     string emptyLabel_    = string("");
 | |
|     int firstCol_ = 8;
 | |
|     int docCol_ = 20;
 | |
|     int lastCol_ = 100;
 | |
|     int indentSize_ = 4;
 | |
|     int maxAltInUsage_ = 1;
 | |
|     int maxAltInDocs_ = 32;
 | |
|     int lineSpc_ = 0;
 | |
|     int paragraphSpc_ = 1;
 | |
|     int groupSplitSize_ = 3;
 | |
|     bool splitTopAlt_ = true;
 | |
|     bool mergeAltCommonPfx_ = false;
 | |
|     bool mergeJoinableCommonPfx_ = true;
 | |
|     bool ignoreNewlines_ = false;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| namespace detail {
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief stream decorator
 | |
|  *        that applies formatting like line wrapping
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class OStream = std::ostream, class StringT = doc_string>
 | |
| class formatting_ostream
 | |
| {
 | |
| public:
 | |
|     using string_type = StringT;
 | |
|     using size_type   = typename string_type::size_type;
 | |
|     using char_type   = typename string_type::value_type;
 | |
| 
 | |
|     formatting_ostream(OStream& os):
 | |
|         os_(os),
 | |
|         curCol_{0}, firstCol_{0}, lastCol_{100},
 | |
|         hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
 | |
|         curBlankLines_{0}, curParagraphLines_{1},
 | |
|         totalNonBlankLines_{0},
 | |
|         ignoreInputNls_{false}
 | |
|     {}
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     const OStream& base() const noexcept { return os_; }
 | |
|           OStream& base()       noexcept { return os_; }
 | |
| 
 | |
|     bool good() const { return os_.good(); }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief determines the leftmost border of the text body */
 | |
|     formatting_ostream& first_column(int c) {
 | |
|         firstCol_ = c < 0 ? 0 : c;
 | |
|         return *this;
 | |
|     }
 | |
|     int first_column() const noexcept { return firstCol_; }
 | |
| 
 | |
|     /** @brief determines the rightmost border of the text body */
 | |
|     formatting_ostream& last_column(int c) {
 | |
|         lastCol_ = c < 0 ? 0 : c;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     int last_column() const noexcept { return lastCol_; }
 | |
| 
 | |
|     int text_width() const noexcept {
 | |
|         return lastCol_ - firstCol_;
 | |
|     }
 | |
| 
 | |
|     /** @brief additional indentation for the 2nd, 3rd, ... line of
 | |
|                a paragraph (sequence of soft-wrapped lines) */
 | |
|     formatting_ostream& hanging_indent(int amount) {
 | |
|         hangingIndent_ = amount;
 | |
|         return *this;
 | |
|     }
 | |
|     int hanging_indent() const noexcept {
 | |
|         return hangingIndent_;
 | |
|     }
 | |
| 
 | |
|     /** @brief amount of blank lines between paragraphs */
 | |
|     formatting_ostream& paragraph_spacing(int lines) {
 | |
|         paragraphSpacing_ = lines;
 | |
|         return *this;
 | |
|     }
 | |
|     int paragraph_spacing() const noexcept {
 | |
|         return paragraphSpacing_;
 | |
|     }
 | |
| 
 | |
|     /** @brief insert paragraph spacing
 | |
|                if paragraph is at least 'lines' lines long */
 | |
|     formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
 | |
|         paragraphSpacingThreshold_ = lines;
 | |
|         return *this;
 | |
|     }
 | |
|     int min_paragraph_lines_for_spacing() const noexcept {
 | |
|         return paragraphSpacingThreshold_;
 | |
|     }
 | |
| 
 | |
|     /** @brief if set to true, newline characters will be ignored */
 | |
|     formatting_ostream& ignore_newline_chars(bool yes) {
 | |
|         ignoreInputNls_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     bool ignore_newline_chars() const noexcept {
 | |
|         return ignoreInputNls_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /* @brief insert 'n' spaces */
 | |
|     void write_spaces(int n) {
 | |
|         if(n < 1) return;
 | |
|         os_ << string_type(size_type(n), ' ');
 | |
|         curCol_ += n;
 | |
|     }
 | |
| 
 | |
|     /* @brief go to new line, but continue current paragraph */
 | |
|     void wrap_soft(int times = 1) {
 | |
|         if(times < 1) return;
 | |
|         if(times > 1) {
 | |
|             os_ << string_type(size_type(times), '\n');
 | |
|         } else {
 | |
|             os_ << '\n';
 | |
|         }
 | |
|         curCol_ = 0;
 | |
|         ++curParagraphLines_;
 | |
|     }
 | |
| 
 | |
|     /* @brief go to new line, and start a new paragraph */
 | |
|     void wrap_hard(int times = 1) {
 | |
|         if(times < 1) return;
 | |
| 
 | |
|         if(paragraph_spacing() > 0 &&
 | |
|            paragraph_lines() >= min_paragraph_lines_for_spacing())
 | |
|         {
 | |
|             times = paragraph_spacing() + 1;
 | |
|         }
 | |
| 
 | |
|         if(times > 1) {
 | |
|             os_ << string_type(size_type(times), '\n');
 | |
|             curBlankLines_ += times - 1;
 | |
|         } else {
 | |
|             os_ << '\n';
 | |
|         }
 | |
|         if(at_begin_of_line()) {
 | |
|             ++curBlankLines_;
 | |
|         }
 | |
|         curCol_ = 0;
 | |
|         curParagraphLines_ = 1;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     bool at_begin_of_line() const noexcept {
 | |
|         return curCol_ <= current_line_begin();
 | |
|     }
 | |
|     int current_line_begin() const noexcept {
 | |
|         return in_hanging_part_of_paragraph()
 | |
|             ? firstCol_ + hangingIndent_
 | |
|             : firstCol_;
 | |
|     }
 | |
| 
 | |
|     int current_column() const noexcept {
 | |
|         return curCol_;
 | |
|     }
 | |
| 
 | |
|     int total_non_blank_lines() const noexcept {
 | |
|         return totalNonBlankLines_;
 | |
|     }
 | |
|     int paragraph_lines() const noexcept {
 | |
|         return curParagraphLines_;
 | |
|     }
 | |
|     int blank_lines_before_paragraph() const noexcept {
 | |
|         return curBlankLines_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     template<class T>
 | |
|     friend formatting_ostream&
 | |
|     operator << (formatting_ostream& os, const T& x) {
 | |
|         os.write(x);
 | |
|         return os;
 | |
|     }
 | |
| 
 | |
|     void flush() {
 | |
|         os_.flush();
 | |
|     }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     bool in_hanging_part_of_paragraph() const noexcept {
 | |
|         return hanging_indent() > 0 && paragraph_lines() > 1;
 | |
|     }
 | |
|     bool current_line_empty() const noexcept {
 | |
|         return curCol_ < 1;
 | |
|     }
 | |
|     bool left_of_text_area() const noexcept {
 | |
|         return curCol_ < current_line_begin();
 | |
|     }
 | |
|     bool right_of_text_area() const noexcept {
 | |
|         return curCol_ > lastCol_;
 | |
|     }
 | |
|     int columns_left_in_line() const noexcept {
 | |
|         return lastCol_ - std::max(current_line_begin(), curCol_);
 | |
|     }
 | |
| 
 | |
|     void fix_indent() {
 | |
|         if(left_of_text_area()) {
 | |
|             const auto fst = current_line_begin();
 | |
|             write_spaces(fst - curCol_);
 | |
|             curCol_ = fst;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     template<class Iter>
 | |
|     bool only_whitespace(Iter first, Iter last) const {
 | |
|         return last == std::find_if_not(first, last,
 | |
|                 [](char_type c) { return std::isspace(c); });
 | |
|     }
 | |
| 
 | |
|     /** @brief write any object */
 | |
|     template<class T>
 | |
|     void write(const T& x) {
 | |
|         std::ostringstream ss;
 | |
|         ss << x;
 | |
|         write(std::move(ss).str());
 | |
|     }
 | |
| 
 | |
|     /** @brief write a stringstream */
 | |
|     void write(const std::ostringstream& s) {
 | |
|         write(s.str());
 | |
|     }
 | |
| 
 | |
|     /** @brief write a string */
 | |
|     void write(const string_type& s) {
 | |
|         write(s.begin(), s.end());
 | |
|     }
 | |
| 
 | |
|     /** @brief partition output into lines */
 | |
|     template<class Iter>
 | |
|     void write(Iter first, Iter last)
 | |
|     {
 | |
|         if(first == last) return;
 | |
|         if(*first == '\n') {
 | |
|             if(!ignore_newline_chars()) wrap_hard();
 | |
|             ++first;
 | |
|             if(first == last) return;
 | |
|         }
 | |
|         auto i = std::find(first, last, '\n');
 | |
|         if(i != last) {
 | |
|             if(ignore_newline_chars()) ++i;
 | |
|             if(i != last) {
 | |
|                 write_line(first, i);
 | |
|                 write(i, last);
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             write_line(first, last);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /** @brief handle line wrapping due to column constraints */
 | |
|     template<class Iter>
 | |
|     void write_line(Iter first, Iter last)
 | |
|     {
 | |
|         if(first == last) return;
 | |
|         if(only_whitespace(first, last)) return;
 | |
| 
 | |
|         if(right_of_text_area()) wrap_soft();
 | |
| 
 | |
|         if(at_begin_of_line()) {
 | |
|             //discard whitespace, it we start a new line
 | |
|             first = std::find_if(first, last,
 | |
|                         [](char_type c) { return !std::isspace(c); });
 | |
|             if(first == last) return;
 | |
|         }
 | |
| 
 | |
|         const auto n = int(std::distance(first,last));
 | |
|         const auto m = columns_left_in_line();
 | |
|         //if text to be printed is too long for one line -> wrap
 | |
|         if(n > m) {
 | |
|             //break before word, if break is mid-word
 | |
|             auto breakat = first + m;
 | |
|             while(breakat > first && !std::isspace(*breakat)) --breakat;
 | |
|             //could not find whitespace before word -> try after the word
 | |
|             if(!std::isspace(*breakat) && breakat == first) {
 | |
|                 breakat = std::find_if(first+m, last,
 | |
|                           [](char_type c) { return std::isspace(c); });
 | |
|             }
 | |
|             if(breakat > first) {
 | |
|                 if(curCol_ < 1) ++totalNonBlankLines_;
 | |
|                 fix_indent();
 | |
|                 std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
 | |
|                 curBlankLines_ = 0;
 | |
|             }
 | |
|             if(breakat < last) {
 | |
|                 wrap_soft();
 | |
|                 write_line(breakat, last);
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             if(curCol_ < 1) ++totalNonBlankLines_;
 | |
|             fix_indent();
 | |
|             std::copy(first, last, std::ostream_iterator<char_type>(os_));
 | |
|             curCol_ += n;
 | |
|             curBlankLines_ = 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /** @brief write a single character */
 | |
|     void write(char_type c)
 | |
|     {
 | |
|         if(c == '\n') {
 | |
|             if(!ignore_newline_chars()) wrap_hard();
 | |
|         }
 | |
|         else {
 | |
|             if(at_begin_of_line()) ++totalNonBlankLines_;
 | |
|             fix_indent();
 | |
|             os_ << c;
 | |
|             ++curCol_;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     OStream& os_;
 | |
|     int curCol_;
 | |
|     int firstCol_;
 | |
|     int lastCol_;
 | |
|     int hangingIndent_;
 | |
|     int paragraphSpacing_;
 | |
|     int paragraphSpacingThreshold_;
 | |
|     int curBlankLines_;
 | |
|     int curParagraphLines_;
 | |
|     int totalNonBlankLines_;
 | |
|     bool ignoreInputNls_;
 | |
| };
 | |
| 
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief   generates usage lines
 | |
|  *
 | |
|  * @details lazily evaluated
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class usage_lines
 | |
| {
 | |
| public:
 | |
|     using string = doc_string;
 | |
| 
 | |
|     usage_lines(const group& cli, string prefix = "",
 | |
|                 const doc_formatting& fmt = doc_formatting{})
 | |
|     :
 | |
|         cli_(cli), fmt_(fmt), prefix_(std::move(prefix))
 | |
|     {
 | |
|         if(!prefix_.empty()) prefix_ += ' ';
 | |
|     }
 | |
| 
 | |
|     usage_lines(const group& cli, const doc_formatting& fmt):
 | |
|         usage_lines(cli, "", fmt)
 | |
|     {}
 | |
| 
 | |
|     usage_lines& ommit_outermost_group_surrounders(bool yes) {
 | |
|         ommitOutermostSurrounders_ = yes;
 | |
|         return *this;
 | |
|     }
 | |
|     bool ommit_outermost_group_surrounders() const {
 | |
|         return ommitOutermostSurrounders_;
 | |
|     }
 | |
| 
 | |
|     template<class OStream>
 | |
|     inline friend OStream& operator << (OStream& os, const usage_lines& p) {
 | |
|         p.write(os);
 | |
|         return os;
 | |
|     }
 | |
| 
 | |
|     string str() const {
 | |
|         std::ostringstream os; os << *this; return os.str();
 | |
|     }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     using stream_t = detail::formatting_ostream<>;
 | |
|     const group& cli_;
 | |
|     doc_formatting fmt_;
 | |
|     string prefix_;
 | |
|     bool ommitOutermostSurrounders_ = false;
 | |
| 
 | |
| 
 | |
|     //-----------------------------------------------------
 | |
|     struct context {
 | |
|         group::depth_first_traverser pos;
 | |
|         std::stack<string> separators;
 | |
|         std::stack<string> postfixes;
 | |
|         int level = 0;
 | |
|         const group* outermost = nullptr;
 | |
|         bool linestart = false;
 | |
|         bool useOutermost = true;
 | |
|         int line = 0;
 | |
| 
 | |
|         bool is_singleton() const noexcept {
 | |
|             return linestart && pos.is_last_in_path();
 | |
|         }
 | |
|         bool is_alternative() const noexcept {
 | |
|             return pos.parent().exclusive();
 | |
|         }
 | |
|     };
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief writes usage text for command line parameters
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     template<class OStream>
 | |
|     void write(OStream& os) const
 | |
|     {
 | |
|         detail::formatting_ostream<OStream> fos(os);
 | |
|         fos.first_column(fmt_.first_column());
 | |
|         fos.last_column(fmt_.last_column());
 | |
| 
 | |
|         auto hindent = int(prefix_.size());
 | |
|         if(fos.first_column() + hindent >= int(0.4 * fos.text_width())) {
 | |
|             hindent = fmt_.indent_size();
 | |
|         }
 | |
|         fos.hanging_indent(hindent);
 | |
| 
 | |
|         fos.paragraph_spacing(fmt_.paragraph_spacing());
 | |
|         fos.min_paragraph_lines_for_spacing(2);
 | |
|         fos.ignore_newline_chars(fmt_.ignore_newline_chars());
 | |
| 
 | |
|         context cur;
 | |
|         cur.pos = cli_.begin_dfs();
 | |
|         cur.linestart = true;
 | |
|         cur.level = cur.pos.level();
 | |
|         cur.outermost = &cli_;
 | |
| 
 | |
|         write(fos, cur, prefix_);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief writes usage text for command line parameters
 | |
|      *
 | |
|      * @param prefix   all that goes in front of current things to print
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     template<class OStream>
 | |
|     void write(OStream& os, context cur, string prefix) const
 | |
|     {
 | |
|         if(!cur.pos) return;
 | |
| 
 | |
|         std::ostringstream buf;
 | |
|         if(cur.linestart) buf << prefix;
 | |
|         const auto initPos = buf.tellp();
 | |
| 
 | |
|         cur.level = cur.pos.level();
 | |
| 
 | |
|         if(cur.useOutermost) {
 | |
|             //we cannot start outside of the outermost group
 | |
|             //so we have to treat it separately
 | |
|             start_group(buf, cur.pos.parent(), cur);
 | |
|             if(!cur.pos) {
 | |
|                 os << buf.str();
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             //don't visit siblings of starter node
 | |
|             cur.pos.skip_siblings();
 | |
|         }
 | |
|         check_end_group(buf, cur);
 | |
| 
 | |
|         do {
 | |
|             if(buf.tellp() > initPos) cur.linestart = false;
 | |
|             if(!cur.linestart && !cur.pos.is_first_in_parent()) {
 | |
|                 buf << cur.separators.top();
 | |
|             }
 | |
|             if(cur.pos->is_group()) {
 | |
|                 start_group(buf, cur.pos->as_group(), cur);
 | |
|                 if(!cur.pos) {
 | |
|                     os << buf.str();
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 buf << param_label(cur.pos->as_param(), cur);
 | |
|                 ++cur.pos;
 | |
|             }
 | |
|             check_end_group(buf, cur);
 | |
|         } while(cur.pos);
 | |
| 
 | |
|         os << buf.str();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief handles pattern group surrounders and separators
 | |
|      *        and alternative splitting
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     void start_group(std::ostringstream& os,
 | |
|                      const group& group, context& cur) const
 | |
|     {
 | |
|         //does cur.pos already point to a member or to group itself?
 | |
|         //needed for special treatment of outermost group
 | |
|         const bool alreadyInside = &(cur.pos.parent()) == &group;
 | |
| 
 | |
|         auto lbl = joined_label(group, cur);
 | |
|         if(!lbl.empty()) {
 | |
|             os << lbl;
 | |
|             cur.linestart = false;
 | |
|             //skip over entire group as its label has already been created
 | |
|             if(alreadyInside) {
 | |
|                 cur.pos.next_after_siblings();
 | |
|             } else {
 | |
|                 cur.pos.next_sibling();
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             const bool splitAlternatives = group.exclusive() &&
 | |
|                 fmt_.split_alternatives() &&
 | |
|                 std::any_of(group.begin(), group.end(),
 | |
|                     [this](const pattern& p) {
 | |
|                         return int(p.param_count()) >= fmt_.alternatives_min_split_size();
 | |
|                     });
 | |
| 
 | |
|             if(splitAlternatives) {
 | |
|                 cur.postfixes.push("");
 | |
|                 cur.separators.push("");
 | |
|                 //recursively print alternative paths in decision-DAG
 | |
|                 //enter group?
 | |
|                 if(!alreadyInside) ++cur.pos;
 | |
|                 cur.linestart = true;
 | |
|                 cur.useOutermost = false;
 | |
|                 auto pfx = os.str();
 | |
|                 os.str("");
 | |
|                 //print paths in DAG starting at each group member
 | |
|                 for(std::size_t i = 0; i < group.size(); ++i) {
 | |
|                     std::stringstream buf;
 | |
|                     cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr;
 | |
|                     write(buf, cur, pfx);
 | |
|                     if(buf.tellp() > int(pfx.size())) {
 | |
|                         os << buf.str();
 | |
|                         if(i < group.size()-1) {
 | |
|                             if(cur.line > 0) {
 | |
|                                 os << string(fmt_.line_spacing(), '\n');
 | |
|                             }
 | |
|                             ++cur.line;
 | |
|                             os << '\n';
 | |
|                         }
 | |
|                     }
 | |
|                     cur.pos.next_sibling(); //do not descend into members
 | |
|                 }
 | |
|                 cur.pos.invalidate(); //signal end-of-path
 | |
|                 return;
 | |
|             }
 | |
|             else {
 | |
|                 //pre & postfixes, separators
 | |
|                 auto surround = group_surrounders(group, cur);
 | |
|                 os << surround.first;
 | |
|                 cur.postfixes.push(std::move(surround.second));
 | |
|                 cur.separators.push(group_separator(group, fmt_));
 | |
|                 //descend into group?
 | |
|                 if(!alreadyInside) ++cur.pos;
 | |
|             }
 | |
|         }
 | |
|         cur.level = cur.pos.level();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     void check_end_group(std::ostringstream& os, context& cur) const
 | |
|     {
 | |
|         for(; cur.level > cur.pos.level(); --cur.level) {
 | |
|             os << cur.postfixes.top();
 | |
|             cur.postfixes.pop();
 | |
|             cur.separators.pop();
 | |
|         }
 | |
|         cur.level = cur.pos.level();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief makes usage label for one command line parameter
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     string param_label(const parameter& p, const context& cur) const
 | |
|     {
 | |
|         const auto& parent = cur.pos.parent();
 | |
| 
 | |
|         const bool startsOptionalSequence =
 | |
|             parent.size() > 1 && p.blocking() && cur.pos.is_first_in_parent();
 | |
| 
 | |
|         const bool outermost =
 | |
|             ommitOutermostSurrounders_ && cur.outermost == &parent;
 | |
| 
 | |
|         const bool showopt = !cur.is_alternative() && !p.required()
 | |
|             && !startsOptionalSequence && !outermost;
 | |
| 
 | |
|         const bool showrep = p.repeatable() && !outermost;
 | |
| 
 | |
|         string lbl;
 | |
| 
 | |
|         if(showrep) lbl += fmt_.repeat_prefix();
 | |
|         if(showopt) lbl += fmt_.optional_prefix();
 | |
| 
 | |
|         const auto& flags = p.flags();
 | |
|         if(!flags.empty()) {
 | |
|             const int n = std::min(fmt_.max_flags_per_param_in_usage(),
 | |
|                                    int(flags.size()));
 | |
| 
 | |
|             const bool surrAlt = n > 1 && !showopt && !cur.is_singleton();
 | |
| 
 | |
|             if(surrAlt) lbl += fmt_.alternative_flags_prefix();
 | |
|             bool sep = false;
 | |
|             for(int i = 0; i < n; ++i) {
 | |
|                 if(sep) {
 | |
|                     if(cur.is_singleton())
 | |
|                         lbl += fmt_.alternative_group_separator();
 | |
|                     else
 | |
|                         lbl += fmt_.flag_separator();
 | |
|                 }
 | |
|                 lbl += flags[i];
 | |
|                 sep = true;
 | |
|             }
 | |
|             if(surrAlt) lbl += fmt_.alternative_flags_postfix();
 | |
|         }
 | |
|         else {
 | |
|              if(!p.label().empty()) {
 | |
|                  lbl += fmt_.label_prefix()
 | |
|                      + p.label()
 | |
|                      + fmt_.label_postfix();
 | |
|              } else if(!fmt_.empty_label().empty()) {
 | |
|                  lbl += fmt_.label_prefix()
 | |
|                      + fmt_.empty_label()
 | |
|                      + fmt_.label_postfix();
 | |
|              } else {
 | |
|                  return "";
 | |
|              }
 | |
|         }
 | |
| 
 | |
|         if(showopt) lbl += fmt_.optional_postfix();
 | |
|         if(showrep) lbl += fmt_.repeat_postfix();
 | |
| 
 | |
|         return lbl;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief prints flags in one group in a merged fashion
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     string joined_label(const group& g, const context& cur) const
 | |
|     {
 | |
|         if(!fmt_.merge_alternative_flags_with_common_prefix() &&
 | |
|            !fmt_.merge_joinable_with_common_prefix()) return "";
 | |
| 
 | |
|         const bool flagsonly = std::all_of(g.begin(), g.end(),
 | |
|             [](const pattern& p){
 | |
|                 return p.is_param() && !p.as_param().flags().empty();
 | |
|             });
 | |
| 
 | |
|         if(!flagsonly) return "";
 | |
| 
 | |
|         const bool showOpt = g.all_optional() &&
 | |
|             !(ommitOutermostSurrounders_ && cur.outermost == &g);
 | |
| 
 | |
|         auto pfx = g.common_flag_prefix();
 | |
|         if(pfx.empty()) return "";
 | |
| 
 | |
|         const auto n = pfx.size();
 | |
|         if(g.exclusive() &&
 | |
|            fmt_.merge_alternative_flags_with_common_prefix())
 | |
|         {
 | |
|             string lbl;
 | |
|             if(showOpt) lbl += fmt_.optional_prefix();
 | |
|             lbl += pfx + fmt_.alternatives_prefix();
 | |
|             bool first = true;
 | |
|             for(const auto& p : g) {
 | |
|                 if(p.is_param()) {
 | |
|                     if(first)
 | |
|                         first = false;
 | |
|                     else
 | |
|                         lbl += fmt_.alternative_param_separator();
 | |
|                     lbl += p.as_param().flags().front().substr(n);
 | |
|                 }
 | |
|             }
 | |
|             lbl += fmt_.alternatives_postfix();
 | |
|             if(showOpt) lbl += fmt_.optional_postfix();
 | |
|             return lbl;
 | |
|         }
 | |
|         //no alternatives, but joinable flags
 | |
|         else if(g.joinable() &&
 | |
|             fmt_.merge_joinable_with_common_prefix())
 | |
|         {
 | |
|             const bool allSingleChar = std::all_of(g.begin(), g.end(),
 | |
|                 [&](const pattern& p){
 | |
|                     return p.is_param() &&
 | |
|                         p.as_param().flags().front().substr(n).size() == 1;
 | |
|                 });
 | |
| 
 | |
|             if(allSingleChar) {
 | |
|                 string lbl;
 | |
|                 if(showOpt) lbl += fmt_.optional_prefix();
 | |
|                 lbl += pfx;
 | |
|                 for(const auto& p : g) {
 | |
|                     if(p.is_param())
 | |
|                         lbl += p.as_param().flags().front().substr(n);
 | |
|                 }
 | |
|                 if(showOpt) lbl += fmt_.optional_postfix();
 | |
|                 return lbl;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return "";
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @return symbols with which to surround a group
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     std::pair<string,string>
 | |
|     group_surrounders(const group& group, const context& cur) const
 | |
|     {
 | |
|         string prefix;
 | |
|         string postfix;
 | |
| 
 | |
|         const bool isOutermost = &group == cur.outermost;
 | |
|         if(isOutermost && ommitOutermostSurrounders_)
 | |
|             return {string{}, string{}};
 | |
| 
 | |
|         if(group.exclusive()) {
 | |
|             if(group.all_optional()) {
 | |
|                 prefix  = fmt_.optional_prefix();
 | |
|                 postfix = fmt_.optional_postfix();
 | |
|                 if(group.all_flagless()) {
 | |
|                     prefix  += fmt_.label_prefix();
 | |
|                     postfix = fmt_.label_prefix() + postfix;
 | |
|                 }
 | |
|             } else if(group.all_flagless()) {
 | |
|                 prefix  = fmt_.label_prefix();
 | |
|                 postfix = fmt_.label_postfix();
 | |
|             } else if(!cur.is_singleton() || !isOutermost) {
 | |
|                 prefix  = fmt_.alternatives_prefix();
 | |
|                 postfix = fmt_.alternatives_postfix();
 | |
|             }
 | |
|         }
 | |
|         else if(group.size() > 1 &&
 | |
|                 group.front().blocking() && !group.front().required())
 | |
|         {
 | |
|             prefix  = fmt_.optional_prefix();
 | |
|             postfix = fmt_.optional_postfix();
 | |
|         }
 | |
|         else if(group.size() > 1 && cur.is_alternative() &&
 | |
|                 &group != cur.outermost)
 | |
|         {
 | |
|             prefix  = fmt_.group_prefix();
 | |
|             postfix = fmt_.group_postfix();
 | |
|         }
 | |
|         else if(!group.exclusive() &&
 | |
|             group.joinable() && !cur.linestart)
 | |
|         {
 | |
|             prefix  = fmt_.joinable_prefix();
 | |
|             postfix = fmt_.joinable_postfix();
 | |
|         }
 | |
| 
 | |
|         if(group.repeatable()) {
 | |
|             if(prefix.empty()) prefix = fmt_.group_prefix();
 | |
|             prefix = fmt_.repeat_prefix() + prefix;
 | |
|             if(postfix.empty()) postfix = fmt_.group_postfix();
 | |
|             postfix += fmt_.repeat_postfix();
 | |
|         }
 | |
| 
 | |
|         return {std::move(prefix), std::move(postfix)};
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @return symbol that separates members of a group
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     static string
 | |
|     group_separator(const group& group, const doc_formatting& fmt)
 | |
|     {
 | |
|         const bool only1ParamPerMember = std::all_of(group.begin(), group.end(),
 | |
|             [](const pattern& p) { return p.param_count() < 2; });
 | |
| 
 | |
|         if(only1ParamPerMember) {
 | |
|             if(group.exclusive()) {
 | |
|                 return fmt.alternative_param_separator();
 | |
|             } else {
 | |
|                 return fmt.param_separator();
 | |
|             }
 | |
|         }
 | |
|         else { //there is at least one large group inside
 | |
|             if(group.exclusive()) {
 | |
|                 return fmt.alternative_group_separator();
 | |
|             } else {
 | |
|                 return fmt.group_separator();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief   generates parameter and group documentation from docstrings
 | |
|  *
 | |
|  * @details lazily evaluated
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class documentation
 | |
| {
 | |
| public:
 | |
|     using string          = doc_string;
 | |
|     using filter_function = std::function<bool(const parameter&)>;
 | |
| 
 | |
|     documentation(const group& cli,
 | |
|                   const doc_formatting& fmt = doc_formatting{},
 | |
|                   filter_function filter = param_filter{})
 | |
|     :
 | |
|         cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{std::move(filter)}
 | |
|     {
 | |
|         //necessary, because we re-use "usage_lines" to generate
 | |
|         //labels for documented groups
 | |
|         usgFmt_.max_flags_per_param_in_usage(
 | |
|             usgFmt_.max_flags_per_param_in_doc());
 | |
|     }
 | |
| 
 | |
|     documentation(const group& cli, filter_function filter) :
 | |
|         documentation{cli, doc_formatting{}, std::move(filter)}
 | |
|     {}
 | |
| 
 | |
|     documentation(const group& cli, const param_filter& filter) :
 | |
|         documentation{cli, doc_formatting{},
 | |
|                       [filter](const parameter& p) { return filter(p); }}
 | |
|     {}
 | |
| 
 | |
|     template<class OStream>
 | |
|     inline friend OStream& operator << (OStream& os, const documentation& p) {
 | |
|         p.write(os);
 | |
|         return os;
 | |
|     }
 | |
| 
 | |
|     string str() const {
 | |
|         std::ostringstream os;
 | |
|         write(os);
 | |
|         return os.str();
 | |
|     }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     using dfs_traverser = group::depth_first_traverser;
 | |
| 
 | |
|     const group& cli_;
 | |
|     doc_formatting fmt_;
 | |
|     doc_formatting usgFmt_;
 | |
|     filter_function filter_;
 | |
|     enum class paragraph { param, group };
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief writes documentation to output stream
 | |
|      *
 | |
|      *******************************************************************/
 | |
|      template<class OStream>
 | |
|      void write(OStream& os) const {
 | |
|         detail::formatting_ostream<OStream> fos(os);
 | |
|         fos.first_column(fmt_.first_column());
 | |
|         fos.last_column(fmt_.last_column());
 | |
|         fos.hanging_indent(0);
 | |
|         fos.paragraph_spacing(0);
 | |
|         fos.ignore_newline_chars(fmt_.ignore_newline_chars());
 | |
|         print_doc(fos, cli_);
 | |
|      }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief writes full documentation text for command line parameters
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     template<class OStream>
 | |
|     void print_doc(detail::formatting_ostream<OStream>& os,
 | |
|                    const group& cli, int indentLvl = 0) const
 | |
|     {
 | |
|         if(cli.empty()) return;
 | |
| 
 | |
|         //if group itself doesn't have docstring
 | |
|         if(cli.doc().empty()) {
 | |
|             for(const auto& p : cli) {
 | |
|                 print_doc(os, p, indentLvl);
 | |
|             }
 | |
|         }
 | |
|         else { //group itself does have docstring
 | |
|             bool anyDocInside = std::any_of(cli.begin(), cli.end(),
 | |
|                 [](const pattern& p){ return !p.doc().empty(); });
 | |
| 
 | |
|             if(anyDocInside) { //group docstring as title, then child entries
 | |
|                 handle_spacing(os, paragraph::group, indentLvl);
 | |
|                 os << cli.doc();
 | |
|                 for(const auto& p : cli) {
 | |
|                     print_doc(os, p, indentLvl + 1);
 | |
|                 }
 | |
|             }
 | |
|             else { //group label first then group docstring
 | |
|                 auto lbl = usage_lines(cli, usgFmt_)
 | |
|                            .ommit_outermost_group_surrounders(true).str();
 | |
| 
 | |
|                 str::trim(lbl);
 | |
|                 handle_spacing(os, paragraph::param, indentLvl);
 | |
|                 print_entry(os, lbl, cli.doc());
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief writes documentation text for one group or parameter
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     template<class OStream>
 | |
|     void print_doc(detail::formatting_ostream<OStream>& os,
 | |
|                    const pattern& ptrn, int indentLvl) const
 | |
|     {
 | |
|         if(ptrn.is_group()) {
 | |
|             print_doc(os, ptrn.as_group(), indentLvl);
 | |
|         }
 | |
|         else {
 | |
|             const auto& p = ptrn.as_param();
 | |
|             if(!filter_(p)) return;
 | |
| 
 | |
|             handle_spacing(os, paragraph::param, indentLvl);
 | |
|             print_entry(os, param_label(p, fmt_), p.doc());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /***************************************************************//**
 | |
|      *
 | |
|      * @brief handles line and paragraph spacings
 | |
|      *
 | |
|      *******************************************************************/
 | |
|     template<class OStream>
 | |
|     void handle_spacing(detail::formatting_ostream<OStream>& os,
 | |
|                         paragraph p, int indentLvl) const
 | |
|     {
 | |
|         const auto oldIndent = os.first_column();
 | |
|         const auto indent = fmt_.first_column() + indentLvl * fmt_.indent_size();
 | |
| 
 | |
|         if(os.total_non_blank_lines() < 1) {
 | |
|             os.first_column(indent);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if(os.paragraph_lines() > 1 || indent < oldIndent) {
 | |
|             os.wrap_hard(fmt_.paragraph_spacing() + 1);
 | |
|         } else {
 | |
|             os.wrap_hard();
 | |
|         }
 | |
| 
 | |
|         if(p == paragraph::group) {
 | |
|             if(os.blank_lines_before_paragraph() < fmt_.paragraph_spacing()) {
 | |
|                 os.wrap_hard(fmt_.paragraph_spacing() - os.blank_lines_before_paragraph());
 | |
|             }
 | |
|         }
 | |
|         else if(os.blank_lines_before_paragraph() < fmt_.line_spacing()) {
 | |
|             os.wrap_hard(fmt_.line_spacing() - os.blank_lines_before_paragraph());
 | |
|         }
 | |
|         os.first_column(indent);
 | |
|     }
 | |
| 
 | |
|     /*********************************************************************//**
 | |
|      *
 | |
|      * @brief prints one entry = label + docstring
 | |
|      *
 | |
|      ************************************************************************/
 | |
|     template<class OStream>
 | |
|     void print_entry(detail::formatting_ostream<OStream>& os,
 | |
|                      const string& label, const string& docstr) const
 | |
|     {
 | |
|         if(label.empty()) return;
 | |
| 
 | |
|         os << label;
 | |
| 
 | |
|         if(!docstr.empty()) {
 | |
|             if(os.current_column() >= fmt_.doc_column()) os.wrap_soft();
 | |
|             const auto oldcol = os.first_column();
 | |
|             os.first_column(fmt_.doc_column());
 | |
|             os << docstr;
 | |
|             os.first_column(oldcol);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /*********************************************************************//**
 | |
|      *
 | |
|      * @brief makes label for one parameter
 | |
|      *
 | |
|      ************************************************************************/
 | |
|     static doc_string
 | |
|     param_label(const parameter& param, const doc_formatting& fmt)
 | |
|     {
 | |
|         doc_string lbl;
 | |
| 
 | |
|         if(param.repeatable()) lbl += fmt.repeat_prefix();
 | |
| 
 | |
|         const auto& flags = param.flags();
 | |
|         if(!flags.empty()) {
 | |
|             lbl += flags[0];
 | |
|             const int n = std::min(fmt.max_flags_per_param_in_doc(),
 | |
|                                    int(flags.size()));
 | |
|             for(int i = 1; i < n; ++i) {
 | |
|                 lbl += fmt.flag_separator() + flags[i];
 | |
|             }
 | |
|         }
 | |
|         else if(!param.label().empty() || !fmt.empty_label().empty()) {
 | |
|             lbl += fmt.label_prefix();
 | |
|             if(!param.label().empty()) {
 | |
|                 lbl += param.label();
 | |
|             } else {
 | |
|                 lbl += fmt.empty_label();
 | |
|             }
 | |
|             lbl += fmt.label_postfix();
 | |
|         }
 | |
| 
 | |
|         if(param.repeatable()) lbl += fmt.repeat_postfix();
 | |
| 
 | |
|         return lbl;
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief stores strings for man page sections
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| class man_page
 | |
| {
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     using string = doc_string;
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     /** @brief man page section */
 | |
|     class section {
 | |
|     public:
 | |
|         using string = doc_string;
 | |
| 
 | |
|         section(string stitle, string scontent):
 | |
|             title_{std::move(stitle)}, content_{std::move(scontent)}
 | |
|         {}
 | |
| 
 | |
|         const string& title()   const noexcept { return title_; }
 | |
|         const string& content() const noexcept { return content_; }
 | |
| 
 | |
|     private:
 | |
|         string title_;
 | |
|         string content_;
 | |
|     };
 | |
| 
 | |
| private:
 | |
|     using section_store = std::vector<section>;
 | |
| 
 | |
| public:
 | |
|     //---------------------------------------------------------------
 | |
|     using value_type     = section;
 | |
|     using const_iterator = section_store::const_iterator;
 | |
|     using size_type      = section_store::size_type;
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     man_page&
 | |
|     append_section(string title, string content)
 | |
|     {
 | |
|         sections_.emplace_back(std::move(title), std::move(content));
 | |
|         return *this;
 | |
|     }
 | |
|     //-----------------------------------------------------
 | |
|     man_page&
 | |
|     prepend_section(string title, string content)
 | |
|     {
 | |
|         sections_.emplace(sections_.begin(),
 | |
|                           std::move(title), std::move(content));
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     const section& operator [] (size_type index) const noexcept {
 | |
|         return sections_[index];
 | |
|     }
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     size_type size() const noexcept { return sections_.size(); }
 | |
| 
 | |
|     bool empty() const noexcept { return sections_.empty(); }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     const_iterator begin() const noexcept { return sections_.begin(); }
 | |
|     const_iterator end()   const noexcept { return sections_.end(); }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     man_page& program_name(const string& n) {
 | |
|         progName_ = n;
 | |
|         return *this;
 | |
|     }
 | |
|     man_page& program_name(string&& n) {
 | |
|         progName_ = std::move(n);
 | |
|         return *this;
 | |
|     }
 | |
|     const string& program_name() const noexcept {
 | |
|         return progName_;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //---------------------------------------------------------------
 | |
|     man_page& section_row_spacing(int rows) {
 | |
|         sectionSpc_ = rows > 0 ? rows : 0;
 | |
|         return *this;
 | |
|     }
 | |
|     int section_row_spacing() const noexcept { return sectionSpc_; }
 | |
| 
 | |
| 
 | |
| private:
 | |
|     int sectionSpc_ = 1;
 | |
|     section_store sections_;
 | |
|     string progName_;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief generates man sections from command line parameters
 | |
|  *        with sections "synopsis" and "options"
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline man_page
 | |
| make_man_page(const group& cli,
 | |
|               doc_string progname = "",
 | |
|               const doc_formatting& fmt = doc_formatting{})
 | |
| {
 | |
|     man_page man;
 | |
|     man.append_section("SYNOPSIS", usage_lines(cli,progname,fmt).str());
 | |
|     man.append_section("OPTIONS", documentation(cli,fmt).str());
 | |
|     return man;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief   generates man page based on command line parameters
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class OStream>
 | |
| OStream&
 | |
| operator << (OStream& os, const man_page& man)
 | |
| {
 | |
|     bool first = true;
 | |
|     const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n');
 | |
|     for(const auto& section : man) {
 | |
|         if(!section.content().empty()) {
 | |
|             if(first) first = false; else os << secSpc;
 | |
|             if(!section.title().empty()) os << section.title() << '\n';
 | |
|             os << section.content();
 | |
|         }
 | |
|     }
 | |
|     os << '\n';
 | |
|     return os;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief printing methods for debugging command line interfaces
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| namespace debug {
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief prints first flag or value label of a parameter
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| inline doc_string doc_label(const parameter& p)
 | |
| {
 | |
|     if(!p.flags().empty()) return p.flags().front();
 | |
|     if(!p.label().empty()) return p.label();
 | |
|     return doc_string{"<?>"};
 | |
| }
 | |
| 
 | |
| inline doc_string doc_label(const group&)
 | |
| {
 | |
|     return "<group>";
 | |
| }
 | |
| 
 | |
| inline doc_string doc_label(const pattern& p)
 | |
| {
 | |
|     return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param());
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief prints parsing result
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class OStream>
 | |
| void print(OStream& os, const parsing_result& result)
 | |
| {
 | |
|     for(const auto& m : result) {
 | |
|         os << "#" << m.index() << " " << m.arg() << " -> ";
 | |
|         auto p = m.param();
 | |
|         if(p) {
 | |
|             os << doc_label(*p) << " \t";
 | |
|             if(m.repeat() > 0) {
 | |
|                 os << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
 | |
|                    <<  m.repeat() << "]";
 | |
|             }
 | |
|             if(m.blocked())  os << " [blocked]";
 | |
|             if(m.conflict()) os << " [conflict]";
 | |
|             os << '\n';
 | |
|         }
 | |
|         else {
 | |
|             os << " [unmapped]\n";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for(const auto& m : result.missing()) {
 | |
|         auto p = m.param();
 | |
|         if(p) {
 | |
|             os << doc_label(*p) << " \t";
 | |
|             os << " [missing after " << m.after_index() << "]\n";
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief prints parameter label and some properties
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class OStream>
 | |
| void print(OStream& os, const parameter& p)
 | |
| {
 | |
|     if(p.greedy()) os << '!';
 | |
|     if(p.blocking()) os << '~';
 | |
|     if(!p.required()) os << '[';
 | |
|     os << doc_label(p);
 | |
|     if(p.repeatable()) os << "...";
 | |
|     if(!p.required()) os << "]";
 | |
| }
 | |
| 
 | |
| 
 | |
| //-------------------------------------------------------------------
 | |
| template<class OStream>
 | |
| void print(OStream& os, const group& g, int level = 0);
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief prints group or parameter; uses indentation
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class OStream>
 | |
| void print(OStream& os, const pattern& param, int level = 0)
 | |
| {
 | |
|     if(param.is_group()) {
 | |
|         print(os, param.as_group(), level);
 | |
|     }
 | |
|     else {
 | |
|         os << doc_string(4*level, ' ');
 | |
|         print(os, param.as_param());
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*************************************************************************//**
 | |
|  *
 | |
|  * @brief prints group and its contents; uses indentation
 | |
|  *
 | |
|  *****************************************************************************/
 | |
| template<class OStream>
 | |
| void print(OStream& os, const group& g, int level)
 | |
| {
 | |
|     auto indent = doc_string(4*level, ' ');
 | |
|     os << indent;
 | |
|     if(g.blocking()) os << '~';
 | |
|     if(g.joinable()) os << 'J';
 | |
|     os << (g.exclusive() ? "(|\n" : "(\n");
 | |
|     for(const auto& p : g) {
 | |
|         print(os, p, level+1);
 | |
|     }
 | |
|     os << '\n' << indent << (g.exclusive() ? "|)" : ")");
 | |
|     if(g.repeatable()) os << "...";
 | |
|     os << '\n';
 | |
| }
 | |
| 
 | |
| 
 | |
| } // namespace debug
 | |
| } //namespace clipp
 | |
| 
 | |
| #endif
 | |
| 
 |