/*****************************************************************************
|
* ___ _ _ ___ ___
|
* | _|| | | | | _ \ _ \ 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
|