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