summaryrefslogtreecommitdiff
path: root/3rdparty
diff options
context:
space:
mode:
authorCalvin Buckley <calvin@cmpct.info>2020-02-11 18:24:37 -0400
committerCalvin Buckley <calvin@cmpct.info>2020-02-11 18:32:26 -0400
commit4946a64db774a140d06c45f186f5bc5a4c0c6daa (patch)
treeebd08b88588ca8390842be86209129e58efd9f25 /3rdparty
parent9fa9857a7514469d062335cd2dcb8c0d06eb624c (diff)
downloadhorizon-4946a64db774a140d06c45f186f5bc5a4c0c6daa.tar.gz
horizon-4946a64db774a140d06c45f186f5bc5a4c0c6daa.tar.bz2
horizon-4946a64db774a140d06c45f186f5bc5a4c0c6daa.tar.xz
horizon-4946a64db774a140d06c45f186f5bc5a4c0c6daa.zip
Use Boost's program_options instead of vendoring clipp
This changes how options are parsed a bit, but tests are happy and typical usage is unaffected; just the usage screen is different. This was intended for post-1.0, but turns out I decided to do it. Boost is already required for pre-C++11, so it doesn't seem like a big deal to use. If you don't build the tools, it won't be required.
Diffstat (limited to '3rdparty')
-rw-r--r--3rdparty/clipp.h7024
1 files changed, 0 insertions, 7024 deletions
diff --git a/3rdparty/clipp.h b/3rdparty/clipp.h
deleted file mode 100644
index d7b101e..0000000
--- a/3rdparty/clipp.h
+++ /dev/null
@@ -1,7024 +0,0 @@
-/*****************************************************************************
- * ___ _ _ ___ ___
- * | _|| | | | | _ \ _ \ 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
-