Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://github.com/splatlab/squeakr
09 January 2021, 06:17:16 UTC
  • Code
  • Branches (7)
  • Releases (3)
  • Visits
    • Branches
    • Releases
    • HEAD
    • refs/heads/development
    • refs/heads/exact
    • refs/heads/large-K
    • refs/heads/master
    • refs/heads/refactoring_new_cli
    • refs/tags/V0.6
    • refs/tags/V0.7
    • v0.5
    • v0.1.master
    • v0.1.large-K
  • 0220a5d
  • /
  • include
  • /
  • clipp.h
Raw File Download
Take a new snapshot of a software origin

If the archived software origin currently browsed is not synchronized with its upstream version (for instance when new commits have been issued), you can explicitly request Software Heritage to take a new snapshot of it.

Use the form below to proceed. Once a request has been submitted and accepted, it will be processed as soon as possible. You can then check its processing state by visiting this dedicated page.
swh spinner

Processing "take a new snapshot" request ...

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge Iframe embedding
swh:1:cnt:a2a0cf8bcb160ad9d0ca5d5132864f4f184f66de
origin badgedirectory badge Iframe embedding
swh:1:dir:734ee87899abe3a16182926cbf8346fbad375c53
origin badgerevision badge
swh:1:rev:a4d4b0ebc64eec5f6862ddbff48ff1de891098ee
origin badgesnapshot badge
swh:1:snp:7c3618ec31c30a936757f656619fa38a7d7b80d8

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Tip revision: a4d4b0ebc64eec5f6862ddbff48ff1de891098ee authored by Prashant Pandey on 06 November 2018, 17:45:37 UTC
merge
Tip revision: a4d4b0e
clipp.h
/*****************************************************************************
 *
 * CLIPP - command line interfaces for modern C++
 *
 * released under MIT license
 *
 * (c) 2017 André Müller; foss@andremueller-online.de
 *
 *****************************************************************************/

#ifndef AM_CLIPP_H__
#define AM_CLIPP_H__

#include <cstring>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#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 && length_ > 0;
    }

    /** @brief returns true, if query is a substring of the query string */
    constexpr explicit operator bool () const noexcept {
        return at_ != arg_string::npos && length_ > 0;
    }

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;

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 && (std::strlen(s) > 0))
            *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{};
    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) {
        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 occured 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 occured
     */
    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 paramete;
     *         the action will be called with the index of
     *         the command line argument where the problem occuredr
     */
    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 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>
{
    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}
    {}

    /** @brief makes "flag" parameter */
    template<class... Strings>
    explicit
    parameter(arg_string str, Strings&&... strs):
        flags_{},
        matcher_{predicate_adapter{match::none}},
        label_{}, required_{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}
    {
        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}
    {}

    /** @brief makes "value" parameter with custom match function
     *         (= partial matcher)
     */
    explicit
    parameter(match_function filter):
        flags_{},
        matcher_{std::move(filter)},
        label_{}, required_{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 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(arg.empty()) return subrange{};

        if(flags_.empty()) {
            return matcher_(arg);
        }
        else {
            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;
    }

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;
};




/*************************************************************************//**
 *
 * @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 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_group() const noexcept {
            if(stack_.empty()) return false;
            return (stack_.back().cur == stack_.back().parent->begin());
        }

        bool is_last_in_group() 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 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*
        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 outermost join group */
        const group*
        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 = 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 skips to next alternative in innermost group
        */
        depth_first_traverser&
        next_alternative() {
            if(stack_.empty()) return *this;

            //find first exclusive group (from the top of the stack!)
            auto i = std::find_if(stack_.rbegin(), stack_.rend(),
                [](const context& c) { return c.parent->exclusive(); });
            if(i == stack_.rend()) return *this;

            stack_.erase(i.base(), stack_.end());
            next_sibling();
            return *this;
        }

        /**
         * @brief
         */
        depth_first_traverser&
        back_to_parent() {
            if(stack_.empty()) 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 taverser 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 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);
}



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& param) {
    return param.joinable(true);
}

inline group&&
joinable(group&& param) {
    return std::move(param.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 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& params)
{
    for(auto& p : params) {
        if(p.is_group()) {
            with_prefix(prefix, p.as_group());
        } else {
            with_prefix(prefix, p.as_param());
        }
    }
    return params;
}


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& params)
{
    for(auto& p : params) {
        if(p.is_group()) {
            with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group());
        } else {
            with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param());
        }
    }
    return params;
}


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 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_{},
        curMatched_{false}, 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* repeat_group() const noexcept { return pos_.repeat_group(); }
    const group* join_group() const noexcept { return pos_.join_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(); curMatched_ = false; }
    bool matched() const noexcept { return curMatched_; }

    bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; }

    //-----------------------------------------------------
    scoped_dfs_traverser&
    next_sibling() { pos_.next_sibling(); return *this; }

    scoped_dfs_traverser&
    next_alternative() { pos_.next_alternative(); 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()) {
            //current group can block if we didn't have any match in it
            if(pos_.is_last_in_group() && pos_.parent().blocking()
                && (!posAfterLastMatch_ || &(posAfterLastMatch_.parent()) != &(pos_.parent())))
            {
                //ascend to parent's level
                ++pos_;
                //skip all siblings of parent group
                pos_.next_after_siblings();
                if(!pos_) return_to_outermost_scope();
            }
            else if(pos_->blocking() && !pos_->is_group()) {
                if(pos_.parent().exclusive()) { //is_alternative(pos_.level())) {
                    pos_.next_alternative();
                } else {
                    //no match => skip siblings of blocking param
                    pos_.next_after_siblings();
                }
                if(!pos_) return_to_outermost_scope();
            } else {
                ++pos_;
            }
        } else {
            ++pos_;
        }
        check_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(!match->blocking() && match.base().parent().blocking()) {
            match.pos_.back_to_parent();
        }

        //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()) {
                    curMatched_ = false;
                    pos_ = std::move(match.pos_);
                    if(!scopes_.empty()) pos_.undo(scopes_.top());
                }
                else { //stay at match position
                    curMatched_ = true;
                    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()) {
                    curMatched_ = false;
                    ++match.pos_;
                } else {
                    curMatched_ = true;
                }

                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)
    {
        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.repeat_group();
        if(!newrg) {
            repeatGroupStarted_ = false;
        }
        else if(lastMatch_.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()
    {
        if(!repeatGroupContinues_) return false;
        const auto curRepGroup = pos_.repeat_group();
        if(!curRepGroup) return false;
        if(curRepGroup != lastMatch_.repeat_group()) return false;
        if(!posAfterLastMatch_) return false;
        return true;
    }

    //-----------------------------------------------------
    void check_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 curMatched_ = false;
    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:
    match_t() = default;
    match_t(arg_string s, scoped_dfs_traverser p):
        str_{std::move(s)}, pos_{std::move(p)}
    {}

    const arg_string& str() const noexcept { return str_; }
    const scoped_dfs_traverser& pos() const noexcept { return pos_; }

    explicit operator bool() const noexcept { return !str_.empty(); }

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 Predicate>
match_t
full_match(scoped_dfs_traverser pos, const arg_string& arg,
           const Predicate& select)
{
    if(arg.empty()) return match_t{};

    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 Predicate>
match_t
prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
             const Predicate& select)
{
    if(arg.empty()) return match_t{};

    while(pos) {
        if(pos->is_param()) {
            const auto& param = pos->as_param();
            if(select(param)) {
                const auto match = param.match(arg);
                if(match.prefix()) {
                    if(match.length() == arg.size()) {
                        return match_t{arg, std::move(pos)};
                    }
                    else {
                        return match_t{arg.substr(match.at(), match.length()),
                                       std::move(pos)};
                    }
                }
            }
        }
        ++pos;
    }
    return match_t{};
}



/*************************************************************************//**
 *
 * @brief finds the first parameter that partially matches a given string;
 *        candidate parameters are traversed using a scoped DFS traverser
 *
 *****************************************************************************/
template<class Predicate>
match_t
partial_match(scoped_dfs_traverser pos, const arg_string& arg,
              const Predicate& select)
{
    if(arg.empty()) return match_t{};

    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_.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() || arg.empty()) 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;
            return false;
        }

        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)
    {
        //Note: flag-params will always take precedence over value-params
        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;
        if(try_match_full(arg, detail::select_values{})) return true;
        if(try_match_joined_sequence(arg, detail::select_all{})) return true;
        if(try_match_joined_params(arg)) return true;
        return false;
    }

    //---------------------------------------------------------------
    template<class Predicate>
    bool try_match_full(const arg_string& arg, const Predicate& select)
    {
        auto match = detail::full_match(pos_, arg, select);

        if(!match) return false;

        add_match(match);
        return true;
    }

    //---------------------------------------------------------------
    template<class Predicate>
    bool try_match_joined_sequence(arg_string arg, const Predicate& acceptFirst)
    {
        auto fstMatch = detail::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.empty() || matches.empty()) return false;

        for(const auto& m : matches) add_match(m);
        return true;
    }

    //-----------------------------------------------------
    bool try_match_joined_flags(const arg_string& arg)
    {
        return try_match_joined([&](const group& g) {
            if(try_match_joined(g, arg, detail::select_flags{},
                                g.common_flag_prefix()) )
            {
                return true;
            }
            return false;
        });
    }

    //---------------------------------------------------------------
    bool try_match_joined_params(const arg_string& arg)
    {
        return try_match_joined([&](const group& g) {
            if(try_match_joined(g, arg, detail::select_all{}) ) {
                return true;
            }
            return false;
        });
    }

    //-----------------------------------------------------
    template<class Predicate>
    bool try_match_joined(const group& joinGroup, arg_string arg,
                          const Predicate& pred,
                          const arg_string& prefix = "")
    {
        parser parse {joinGroup};
        std::vector<match_t> matches;

        while(!arg.empty()) {
            auto match = detail::prefix_match(parse.pos_, arg, pred);

            if(!match) return false;

            arg.erase(0, match.str().size());
            //make sure prefix is always present after the first match
            //ensures 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 Predicate>
    bool try_match_joined(const Predicate& pred)
    {
        if(pos_ && pos_.parent().joinable()) {
            const auto& g = pos_.parent();
            if(pred(g)) return true;
            return false;
        }

        auto pos = pos_;
        while(pos) {
            if(pos->is_group() && pos->as_group().joinable()) {
                const auto& g = pos->as_group();
                if(pred(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() || match.str().empty()) 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.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.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
    {
        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 redult */
    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 occured */
    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 occured */
    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 insertion, no deletion)
 *
 *****************************************************************************/
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) {
        if(std::strlen(a) > 0) 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 exclusive_  = tri::either;
    tri hasDoc_     = tri::yes;
};






/*************************************************************************//**
 *
 * @brief documentation formatting options
 *
 *****************************************************************************/
class doc_formatting
{
public:
    using string = doc_string;

    /** @brief determines column where documentation printing starts */
    doc_formatting& start_column(int col) { startCol_ = col; return *this; }
    int             start_column() const noexcept { return startCol_; }

    /** @brief determines column where docstrings start */
    doc_formatting& doc_column(int col) { docCol_ = col; return *this; }
    int             doc_column() const noexcept  { return docCol_; }

    /** @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 determnines 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 determnines 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 determnines 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 determnines 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 determnines 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 determnines 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 determnines 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_; }

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 startCol_ = 8;
    int docCol_ = 20;
    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;
};




/*************************************************************************//**
 *
 * @brief   generates usage lines
 *
 * @details lazily evaluated
 *
 *****************************************************************************/
class usage_lines
{
public:
    using string = doc_string;

    usage_lines(const group& params, string prefix = "",
                const doc_formatting& fmt = doc_formatting{})
    :
        params_(params), fmt_(fmt), prefix_(std::move(prefix))
    {
        if(!prefix_.empty()) prefix_ += ' ';
        if(fmt_.start_column() > 0) prefix_.insert(0, fmt.start_column(), ' ');
    }

    usage_lines(const group& params, const doc_formatting& fmt):
        usage_lines(params, "", 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.print_usage(os);
        return os;
    }

    string str() const {
        std::ostringstream os; os << *this; return os.str();
    }


private:
    const group& params_;
    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 print_usage(OStream& os) const
    {
        context cur;
        cur.pos = params_.begin_dfs();
        cur.linestart = true;
        cur.level = cur.pos.level();
        cur.outermost = &params_;

        print_usage(os, 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 print_usage(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_group()) {
                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;
                    print_usage(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 memebers
                }
                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_group();

        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& params, 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(params.begin(), params.end(),
            [](const pattern& p){
                return p.is_param() && !p.as_param().flags().empty();
            });

        if(!flagsonly) return "";

        const bool showOpt = params.all_optional() &&
            !(ommitOutermostSurrounders_ && cur.outermost == &params);

        auto pfx = params.common_flag_prefix();
        if(pfx.empty()) return "";

        const auto n = pfx.size();
        if(params.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 : params) {
                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(params.joinable() &&
            fmt_.merge_joinable_with_common_prefix())
        {
            const bool allSingleChar = std::all_of(params.begin(), params.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 : params) {
                    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;

    documentation(const group& cli,
                  const doc_formatting& fmt = doc_formatting{},
                  const param_filter& filter = param_filter{})
    :
        cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{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& params,
                  const param_filter& filter,
                  const doc_formatting& fmt = doc_formatting{})
    :
        documentation(params, fmt, filter)
    {}

    template<class OStream>
    inline friend OStream& operator << (OStream& os, const documentation& p) {
        printed prn = printed::nothing;
        p.print_doc(os, p.cli_, prn);
        return os;
    }

    string str() const {
        std::ostringstream os; os << *this; return os.str();
    }


private:
    using dfs_traverser = group::depth_first_traverser;
    enum class printed { nothing, line, paragraph };

    const group& cli_;
    doc_formatting fmt_;
    doc_formatting usgFmt_;
    param_filter filter_;


    /***************************************************************//**
     *
     * @brief writes full documentation text for command line parameters
     *
     *******************************************************************/
    template<class OStream>
    void print_doc(OStream& os, const group& params,
                   printed& sofar,
                   int indentLvl = 0) const
    {
        if(params.empty()) return;

        //if group itself doesn't have docstring
        if(params.doc().empty()) {
            for(const auto& p : params) {
                print_doc(os, p, sofar, indentLvl);
            }
        }
        else { //group itself does have docstring
            bool anyDocInside = std::any_of(params.begin(), params.end(),
                [](const pattern& p){ return !p.doc().empty(); });

            if(anyDocInside) { //group docstring as title, then child entries
                if(sofar != printed::nothing) {
                    os << string(fmt_.paragraph_spacing() + 1, '\n');
                }
                auto indent = string(fmt_.start_column(), ' ');
                if(indentLvl > 0) indent += string(fmt_.indent_size() * indentLvl, ' ');
                os << indent << params.doc() << '\n';
                sofar = printed::nothing;
                for(const auto& p : params) {
                    print_doc(os, p, sofar, indentLvl + 1);
                }
                sofar = printed::paragraph;
            }
            else { //group label first then group docstring
                auto lbl = usage_lines(params, usgFmt_)
                           .ommit_outermost_group_surrounders(true).str();

                str::trim(lbl);
                print_entry(os, lbl, params.doc(), fmt_, sofar, indentLvl);
            }
        }
    }


    /***************************************************************//**
     *
     * @brief writes documentation text for one group or parameter
     *
     *******************************************************************/
    template<class OStream>
    void print_doc(OStream& os, const pattern& ptrn,
                   printed& sofar, int indentLvl) const
    {
        if(ptrn.is_group()) {
            print_doc(os, ptrn.as_group(), sofar, indentLvl);
        }
        else {
            const auto& p = ptrn.as_param();
            if(!filter_(p)) return;
            print_entry(os, param_label(p, fmt_), p.doc(), fmt_, sofar, indentLvl);
        }
    }


    /*********************************************************************//**
     *
     * @brief prints one entry = label + docstring
     *
     ************************************************************************/
    template<class OStream>
    static void
    print_entry(OStream& os,
                const string& label, const string& docstr,
                const doc_formatting& fmt, printed& sofar, int indentLvl)
    {
        if(label.empty()) return;

        auto indent = string(fmt.start_column(), ' ');
        if(indentLvl > 0) indent += string(fmt.indent_size() * indentLvl, ' ');

        const auto len = int(indent.size() + label.size());
        const bool oneline = len < fmt.doc_column();

        if(oneline) {
            if(sofar == printed::line)
                os << string(fmt.line_spacing() + 1, '\n');
            else if(sofar == printed::paragraph)
                os << string(fmt.paragraph_spacing() + 1, '\n');
        }
        else if(sofar != printed::nothing) {
            os << string(fmt.paragraph_spacing() + 1, '\n');
        }

        sofar = oneline ? printed::line : printed::paragraph;

        os << indent << label;

        if(!docstr.empty()) {
            if(oneline) {
                os << string(fmt.doc_column() - len, ' ');
            } else {
                os << '\n' << string(fmt.doc_column(), ' ');
            }
            os << docstr;
        }
    }


    /*********************************************************************//**
     *
     * @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& params,
              doc_string progname = "",
              const doc_formatting& fmt = doc_formatting{})
{
    man_page man;
    man.append_section("SYNOPSIS", usage_lines(params,progname,fmt).str());
    man.append_section("OPTIONS", documentation(params,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.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

back to top

Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API