From 5241f9783b41e74f719dc4a472c6b0803b0eff8c Mon Sep 17 00:00:00 2001 From: a1xd <68629610+a1xd@users.noreply.github.com> Date: Fri, 31 Jul 2020 20:04:19 -0400 Subject: add read add function that makes an ioctl call to return the driver's active mouse_modifier --- common/common.vcxitems | 1 + common/external/tagged-union-single.h | 2 +- common/rawaccel-io.hpp | 46 +++++++++++++++++++++++++ driver/driver.cpp | 63 +++++++++++++++++++++++------------ 4 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 common/rawaccel-io.hpp diff --git a/common/common.vcxitems b/common/common.vcxitems index d1e8db0..aeeaa95 100644 --- a/common/common.vcxitems +++ b/common/common.vcxitems @@ -23,6 +23,7 @@ + diff --git a/common/external/tagged-union-single.h b/common/external/tagged-union-single.h index 3353325..f0de097 100644 --- a/common/external/tagged-union-single.h +++ b/common/external/tagged-union-single.h @@ -137,7 +137,7 @@ struct tagged_union { int tag = 0; struct storage_t { - alignas(max_align_of) char bytes[max_size_of] = ""; + alignas(max_align_of) char bytes[max_size_of] = {}; template inline constexpr T& as() { diff --git a/common/rawaccel-io.hpp b/common/rawaccel-io.hpp new file mode 100644 index 0000000..5d6fad6 --- /dev/null +++ b/common/rawaccel-io.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#define NOMINMAX +#include + +#include "rawaccel.hpp" + +#define RA_IOCTL CTL_CODE(0x8888, 0x888, METHOD_BUFFERED, FILE_ANY_ACCESS) + +namespace rawaccel { + + mouse_modifier read() { + HANDLE ra_handle = INVALID_HANDLE_VALUE; + + ra_handle = CreateFileW(L"\\\\.\\rawaccel", 0, 0, 0, OPEN_EXISTING, 0, 0); + + if (ra_handle == INVALID_HANDLE_VALUE) { + throw std::system_error(GetLastError(), std::system_category(), "CreateFile failed"); + } + + mouse_modifier mod; + DWORD dummy; + + BOOL success = DeviceIoControl( + ra_handle, + RA_IOCTL, + NULL, // input buffer + 0, // input buffer size + &mod, // output buffer + sizeof(mouse_modifier), // output buffer size + &dummy, // bytes returned + NULL // overlapped structure + ); + + CloseHandle(ra_handle); + + if (!success) { + throw std::system_error(GetLastError(), std::system_category(), "DeviceIoControl failed"); + } + + return mod; + } + +} diff --git a/driver/driver.cpp b/driver/driver.cpp index 1f9cebd..c893b8b 100644 --- a/driver/driver.cpp +++ b/driver/driver.cpp @@ -143,41 +143,60 @@ Return Value: --*/ { NTSTATUS status; - void* input_buffer; - size_t input_size; + void* buffer; + size_t size; UNREFERENCED_PARAMETER(Queue); - UNREFERENCED_PARAMETER(OutputBufferLength); UNREFERENCED_PARAMETER(IoControlCode); PAGED_CODE(); DebugPrint(("Ioctl received into filter control object.\n")); - if (InputBufferLength != sizeof(ra::mouse_modifier)) { - DebugPrint(("Received unknown request of %u bytes\n", InputBufferLength)); - // status maps to win32 error code 1784: ERROR_INVALID_USER_BUFFER - WdfRequestComplete(Request, STATUS_INVALID_BUFFER_SIZE); - return; - } + if (InputBufferLength == sizeof(ra::mouse_modifier)) { + status = WdfRequestRetrieveInputBuffer( + Request, + sizeof(ra::mouse_modifier), + &buffer, + &size + ); - status = WdfRequestRetrieveInputBuffer( - Request, - sizeof(ra::mouse_modifier), - &input_buffer, - &input_size - ); + if (!NT_SUCCESS(status)) { + DebugPrint(("RetrieveInputBuffer failed: 0x%x\n", status)); + // status maps to win32 error code 1359: ERROR_INTERNAL_ERROR + WdfRequestComplete(Request, STATUS_MESSAGE_LOST); + return; + } - if (!NT_SUCCESS(status)) { - DebugPrint(("RetrieveInputBuffer failed: 0x%x\n", status)); - // status maps to win32 error code 1359: ERROR_INTERNAL_ERROR - WdfRequestComplete(Request, STATUS_MESSAGE_LOST); - return; + global.modifier = *reinterpret_cast(buffer); + + WdfRequestComplete(Request, STATUS_SUCCESS); } + else if (OutputBufferLength == sizeof(ra::mouse_modifier)) { + status = WdfRequestRetrieveOutputBuffer( + Request, + sizeof(ra::mouse_modifier), + &buffer, + &size + ); + + if (!NT_SUCCESS(status)) { + DebugPrint(("RetrieveOutputBuffer failed: 0x%x\n", status)); + // status maps to win32 error code 1359: ERROR_INTERNAL_ERROR + WdfRequestComplete(Request, STATUS_MESSAGE_LOST); + return; + } + + *reinterpret_cast(buffer) = global.modifier; - global.modifier = *reinterpret_cast(input_buffer); + WdfRequestComplete(Request, STATUS_SUCCESS); + } + else { + DebugPrint(("Received unknown request: in %uB, out %uB\n", InputBufferLength, OutputBufferLength)); + // status maps to win32 error code 1784: ERROR_INVALID_USER_BUFFER + WdfRequestComplete(Request, STATUS_INVALID_BUFFER_SIZE); + } - WdfRequestComplete(Request, STATUS_SUCCESS); } #pragma warning(pop) // enable 28118 again -- cgit v1.2.3 From 66a4043a9ecb1990878bea230f213708c7fdd3da Mon Sep 17 00:00:00 2001 From: a1xd <68629610+a1xd@users.noreply.github.com> Date: Fri, 31 Jul 2020 20:19:24 -0400 Subject: move write function into common io header --- common/rawaccel-io.hpp | 29 +++++++++++++++++++++++++++++ console/console.cpp | 9 ++++----- console/console.vcxproj | 2 -- console/console_write.cpp | 32 -------------------------------- console/console_write.hpp | 14 -------------- rawaccel.sln | 3 +-- wrapper/wrapper.cpp | 1 + wrapper/wrapper.hpp | 14 ++++++-------- wrapper/wrapper.vcxproj | 1 + wrapper/wrapper_writer.cpp | 4 ++-- wrapper/wrapper_writer.hpp | 2 +- 11 files changed, 45 insertions(+), 66 deletions(-) delete mode 100644 console/console_write.cpp delete mode 100644 console/console_write.hpp diff --git a/common/rawaccel-io.hpp b/common/rawaccel-io.hpp index 5d6fad6..7a4c59c 100644 --- a/common/rawaccel-io.hpp +++ b/common/rawaccel-io.hpp @@ -43,4 +43,33 @@ namespace rawaccel { return mod; } + void write(mouse_modifier mod) { + HANDLE ra_handle = INVALID_HANDLE_VALUE; + + ra_handle = CreateFileW(L"\\\\.\\rawaccel", 0, 0, 0, OPEN_EXISTING, 0, 0); + + if (ra_handle == INVALID_HANDLE_VALUE) { + throw std::system_error(GetLastError(), std::system_category(), "CreateFile failed"); + } + + DWORD dummy; + + BOOL success = DeviceIoControl( + ra_handle, + RA_IOCTL, + &mod, // input buffer + sizeof(mouse_modifier), // input buffer size + NULL, // output buffer + 0, // output buffer size + &dummy, // bytes returned + NULL // overlapped structure + ); + + CloseHandle(ra_handle); + + if (!success) { + throw std::system_error(GetLastError(), std::system_category(), "DeviceIoControl failed"); + } + } + } diff --git a/console/console.cpp b/console/console.cpp index 549cb68..00dc481 100644 --- a/console/console.cpp +++ b/console/console.cpp @@ -1,14 +1,13 @@ #include -#define NOMINMAX -#include - #include -#include "console_write.hpp" +#include + +namespace ra = rawaccel; int main(int argc, char** argv) { try { - write(ra::parse(argc, argv)); + ra::write(ra::parse(argc, argv)); } catch (std::domain_error e) { std::cerr << e.what() << '\n'; diff --git a/console/console.vcxproj b/console/console.vcxproj index 05780cd..d0ad292 100644 --- a/console/console.vcxproj +++ b/console/console.vcxproj @@ -90,8 +90,6 @@ - - diff --git a/console/console_write.cpp b/console/console_write.cpp deleted file mode 100644 index 3240ea5..0000000 --- a/console/console_write.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "console_write.hpp" - -void write(ra::mouse_modifier vars) { - HANDLE ra_handle = INVALID_HANDLE_VALUE; - - ra_handle = CreateFileW(L"\\\\.\\rawaccel", 0, 0, 0, OPEN_EXISTING, 0, 0); - - if (ra_handle == INVALID_HANDLE_VALUE) { - throw std::system_error(GetLastError(), std::system_category(), "CreateFile failed"); - } - - DWORD dummy; - - BOOL success = DeviceIoControl( - ra_handle, - RA_WRITE, - &vars, - sizeof(ra::mouse_modifier), - NULL, // output buffer - 0, // output buffer size - &dummy, // bytes returned - NULL // overlapped structure - ); - - CloseHandle(ra_handle); - - if (!success) { - throw std::system_error(GetLastError(), std::system_category(), "DeviceIoControl failed"); - } -} diff --git a/console/console_write.hpp b/console/console_write.hpp deleted file mode 100644 index 31eb575..0000000 --- a/console/console_write.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -#define NOMINMAX -#include - -#include "..\common\rawaccel.hpp" - -#define RA_WRITE CTL_CODE(0x8888, 0x888, METHOD_BUFFERED, FILE_ANY_ACCESS) - -namespace ra = rawaccel; - -void write(ra::mouse_modifier vars); \ No newline at end of file diff --git a/rawaccel.sln b/rawaccel.sln index 2235818..0db5ecb 100644 --- a/rawaccel.sln +++ b/rawaccel.sln @@ -23,10 +23,9 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution common-install\common-install.vcxitems*{058d66c6-d88b-4fdb-b0e4-0a6fe7483b95}*SharedItemsImports = 9 common\common.vcxitems*{24b4226f-1461-408f-a1a4-1371c97153ea}*SharedItemsImports = 9 - common\common.vcxitems*{60d6c942-ac20-4c05-a2be-54b5c966534d}*SharedItemsImports = 4 + common\common.vcxitems*{28a3656f-a1de-405c-b547-191c32ec555f}*SharedItemsImports = 4 common-install\common-install.vcxitems*{896950d1-520a-420a-b6b1-73014b92a68c}*SharedItemsImports = 4 common-install\common-install.vcxitems*{a4097ff6-a6f0-44e8-b8d0-538d0fb75936}*SharedItemsImports = 4 - common\common.vcxitems*{ab7b3759-b85f-4067-8935-fb4539b41869}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/wrapper/wrapper.cpp b/wrapper/wrapper.cpp index ceee1a1..bd0574f 100644 --- a/wrapper/wrapper.cpp +++ b/wrapper/wrapper.cpp @@ -1,6 +1,7 @@ #pragma once #include "wrapper.hpp" + using namespace rawaccel; using namespace System; diff --git a/wrapper/wrapper.hpp b/wrapper/wrapper.hpp index 870aca7..e8f100d 100644 --- a/wrapper/wrapper.hpp +++ b/wrapper/wrapper.hpp @@ -1,16 +1,14 @@ #pragma once -#include "wrapper_writer.hpp" -#include "..\common\rawaccel.hpp"; -#include "..\common\accel-error.hpp"; #include -using namespace rawaccel; -using namespace System; +#include +#include -public value struct ArgsWrapper { - int a; -}; +#include "wrapper_writer.hpp" + +using namespace rawaccel; +using namespace System; public ref class ManagedAccel { diff --git a/wrapper/wrapper.vcxproj b/wrapper/wrapper.vcxproj index 28acbe7..bffbf8b 100644 --- a/wrapper/wrapper.vcxproj +++ b/wrapper/wrapper.vcxproj @@ -59,6 +59,7 @@ + diff --git a/wrapper/wrapper_writer.cpp b/wrapper/wrapper_writer.cpp index 0a74105..da7c9bf 100644 --- a/wrapper/wrapper_writer.cpp +++ b/wrapper/wrapper_writer.cpp @@ -1,9 +1,9 @@ #pragma once -#include "..\console\console_write.cpp" +#include #include "wrapper_writer.hpp" void writer::writeToDriver(rawaccel::mouse_modifier* modifier) { - write(*modifier); + rawaccel::write(*modifier); } diff --git a/wrapper/wrapper_writer.hpp b/wrapper/wrapper_writer.hpp index 591f62f..dcbef10 100644 --- a/wrapper/wrapper_writer.hpp +++ b/wrapper/wrapper_writer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "..\common\rawaccel.hpp" +#include struct writer { void writeToDriver(rawaccel::mouse_modifier* modifier); -- cgit v1.2.3 From b49a91627faa6411023f7823250337cc1a71af82 Mon Sep 17 00:00:00 2001 From: a1xd <68629610+a1xd@users.noreply.github.com> Date: Fri, 31 Jul 2020 20:36:17 -0400 Subject: move clipp/parse logic into console project --- common/common.vcxitems | 2 +- common/external/clipp.h | 7027 --------------------------------- common/external/nillable.h | 30 - common/external/tagged-union-single.h | 202 - common/rawaccel-io.hpp | 6 + common/rawaccel-userspace.hpp | 128 - common/rawaccel.hpp | 2 +- common/tagged-union-single.h | 202 + console/console.cpp | 3 +- console/console.vcxproj | 3 + console/external/clipp.h | 7027 +++++++++++++++++++++++++++++++++ console/parse.hpp | 128 + rawaccel.sln | 2 +- 13 files changed, 7371 insertions(+), 7391 deletions(-) delete mode 100644 common/external/clipp.h delete mode 100644 common/external/nillable.h delete mode 100644 common/external/tagged-union-single.h delete mode 100644 common/rawaccel-userspace.hpp create mode 100644 common/tagged-union-single.h create mode 100644 console/external/clipp.h create mode 100644 console/parse.hpp diff --git a/common/common.vcxitems b/common/common.vcxitems index aeeaa95..7102164 100644 --- a/common/common.vcxitems +++ b/common/common.vcxitems @@ -24,8 +24,8 @@ - + diff --git a/common/external/clipp.h b/common/external/clipp.h deleted file mode 100644 index cca1554..0000000 --- a/common/external/clipp.h +++ /dev/null @@ -1,7027 +0,0 @@ -/***************************************************************************** - * ___ _ _ ___ ___ - * | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++ - * | |_ | |_ | | | _/ _/ version 1.2.3 - * |___||___||_| |_| |_| https://github.com/muellan/clipp - * - * Licensed under the MIT License . - * Copyright (c) 2017-2018 André Müller - * - * --------------------------------------------------------------------------- - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/*************************************************************************//** - * - * @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; - - - -/*************************************************************************//** - * - * @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; -using match_function = std::function; - - - - - - -/*************************************************************************//** - * - * @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 -constexpr auto -check_is_callable(int) -> decltype( - std::declval()(std::declval()...), - std::integral_constant>::value>{} ); - -template -constexpr auto -check_is_callable(long) -> std::false_type; - -template -constexpr auto -check_is_callable_without_arg(int) -> decltype( - std::declval()(), - std::integral_constant>::value>{} ); - -template -constexpr auto -check_is_callable_without_arg(long) -> std::false_type; - - - -template -constexpr auto -check_is_void_callable(int) -> decltype( - std::declval()(std::declval()...), std::true_type{}); - -template -constexpr auto -check_is_void_callable(long) -> std::false_type; - -template -constexpr auto -check_is_void_callable_without_arg(int) -> decltype( - std::declval()(), std::true_type{}); - -template -constexpr auto -check_is_void_callable_without_arg(long) -> std::false_type; - - - -template -struct is_callable; - - -template -struct is_callable : - decltype(check_is_callable(0)) -{}; - -template -struct is_callable : - decltype(check_is_callable_without_arg(0)) -{}; - - -template -struct is_callable : - decltype(check_is_void_callable(0)) -{}; - -template -struct is_callable : - decltype(check_is_void_callable_without_arg(0)) -{}; - - - -/*************************************************************************//** - * - * @brief input range type trait - * - *****************************************************************************/ -template -constexpr auto -check_is_input_range(int) -> decltype( - begin(std::declval()), end(std::declval()), - std::true_type{}); - -template -constexpr auto -check_is_input_range(char) -> decltype( - std::begin(std::declval()), std::end(std::declval()), - std::true_type{}); - -template -constexpr auto -check_is_input_range(long) -> std::false_type; - -template -struct is_input_range : - decltype(check_is_input_range(0)) -{}; - - - -/*************************************************************************//** - * - * @brief size() member type trait - * - *****************************************************************************/ -template -constexpr auto -check_has_size_getter(int) -> - decltype(std::declval().size(), std::true_type{}); - -template -constexpr auto -check_has_size_getter(long) -> std::false_type; - -template -struct has_size_getter : - decltype(check_has_size_getter(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 sizeof(T))> -struct limits_clamped { - static T from(const V& v) { - if(v >= V(std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - if(v <= V(std::numeric_limits::lowest())) { - return std::numeric_limits::lowest(); - } - return T(v); - } -}; - -template -struct limits_clamped { - static T from(const V& v) { return T(v); } -}; - - -/*************************************************************************//** - * - * @brief returns value of v as a T, clamped at T's maximum - * - *****************************************************************************/ -template -inline T clamped_on_limits(const V& v) { - return limits_clamped::from(v); -} - - - - -/*************************************************************************//** - * - * @brief type conversion helpers - * - *****************************************************************************/ -template -struct make { - static inline T from(const char* s) { - if(!s) return false; - //a conversion from const char* to / must exist - return static_cast(s); - } -}; - -template<> -struct make { - static inline bool from(const char* s) { - if(!s) return false; - return static_cast(s); - } -}; - -template<> -struct make { - static inline unsigned char from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned short int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned long int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned long long int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - 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(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline short int from(const char* s) { - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline int from(const char* s) { - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline long int from(const char* s) { - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline long long int from(const char* s) { - return (std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline float from(const char* s) { - return (std::strtof(s,nullptr)); - } -}; - -template<> -struct make { - static inline double from(const char* s) { - return (std::strtod(s,nullptr)); - } -}; - -template<> -struct make { - static inline long double from(const char* s) { - return (std::strtold(s,nullptr)); - } -}; - -template<> -struct make { - static inline std::string from(const char* s) { - return std::string(s); - } -}; - - - -/*************************************************************************//** - * - * @brief assigns boolean constant to one or multiple target objects - * - *****************************************************************************/ -template -class assign_value -{ -public: - template - explicit constexpr - assign_value(T& target, X&& value) noexcept : - t_{std::addressof(target)}, v_{std::forward(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 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 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 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 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::from(s); - } - -private: - T* t_; -}; - - -//------------------------------------------------------------------- -/** - * @brief specialization for vectors: append element - */ -template -class map_arg_to> -{ -public: - map_arg_to(std::vector& target): t_{std::addressof(target)} {} - - void operator () (const char* s) const { - if(t_ && s) t_->push_back(detail::make::from(s)); - } - -private: - std::vector* t_; -}; - - -//------------------------------------------------------------------- -/** - * @brief specialization for bools: - * set to true regardless of string content - */ -template<> -class map_arg_to -{ -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 -T make(const arg_string& s) -{ - return detail::make::from(s); -} - - - -/*************************************************************************//** - * - * @brief removes trailing whitespace from string - * - *****************************************************************************/ -template -inline void -trimr(std::basic_string& 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 -inline void -triml(std::basic_string& 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 -inline void -trim(std::basic_string& s) -{ - triml(s); - trimr(s); -} - - -/*************************************************************************//** - * - * @brief removes all whitespaces from string - * - *****************************************************************************/ -template -inline void -remove_ws(std::basic_string& 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 -inline bool -has_prefix(const std::basic_string& subject, - const std::basic_string& 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 -inline bool -has_postfix(const std::basic_string& subject, - const std::basic_string& 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 -auto -longest_common_prefix(const InputRange& strs) - -> typename std::decay::type -{ - static_assert(traits::is_input_range(), - "parameter must satisfy the InputRange concept"); - - static_assert(traits::has_size_getter< - typename std::decay::type>(), - "elements of input range must have a ::size() member function"); - - using std::begin; - using std::end; - - using item_t = typename std::decay::type; - using str_size_t = typename std::decaysize())>::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 -subrange -longest_substring_match(const std::basic_string& arg, - const InputRange& substrings) -{ - using string_t = std::basic_string; - - static_assert(traits::is_input_range(), - "parameter must satisfy the InputRange concept"); - - static_assert(std::is_same::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 -subrange -longest_prefix_match(const std::basic_string& arg, - const InputRange& prefixes) -{ - using string_t = std::basic_string; - using s_size_t = typename string_t::size_type; - - static_assert(traits::is_input_range(), - "parameter must satisfy the InputRange concept"); - - static_assert(std::is_same::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 -inline subrange -substring_match(const std::basic_string& subject, - const std::basic_string& 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::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 -subrange -first_number_match(std::basic_string s, - C digitSeparator = C(','), - C decimalPoint = C('.'), - C exponential = C('e')) -{ - using string_t = std::basic_string; - 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; - - // overwritten to match numbers without leading 0, - // also commented out call to sanitize_args in parse - auto i = s.find_first_of("0123456789+-."); - if (i == string_t::npos) return subrange{}; - bool point = s[i] == decimalPoint; - - 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 -subrange -first_integer_match(std::basic_string s, - C digitSeparator = C(',')) -{ - using string_t = std::basic_string; - 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 -bool represents_number(const std::basic_string& 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 -bool represents_integer(const std::basic_string& 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 -inline detail::assign_value -set(T& target, V value) { - return detail::assign_value{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 -inline detail::map_arg_to -set(T& target) { - return detail::map_arg_to{target}; -} - - - -/*************************************************************************//** - * - * @brief makes function object that sets a bool to true - * - *****************************************************************************/ -inline detail::assign_value -set(bool& target) { - return detail::assign_value{target,true}; -} - -/*************************************************************************//** - * - * @brief makes function object that sets a bool to false - * - *****************************************************************************/ -inline detail::assign_value -unset(bool& target) { - return detail::assign_value{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 -inline detail::increment -increment(T& target) { - return detail::increment{target}; -} - -/*************************************************************************//** - * - * @brief makes function object that decrements using operator -- - * - *****************************************************************************/ -template -inline detail::increment_by -increment(T& target, T by) { - return detail::increment_by{target, std::move(by)}; -} - -/*************************************************************************//** - * - * @brief makes function object that increments by a fixed amount using operator += - * - *****************************************************************************/ -template -inline detail::decrement -decrement(T& target) { - return detail::decrement{target}; -} - - - - - - -/*************************************************************************//** - * - * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) - * - *****************************************************************************/ -namespace detail { - - -/*************************************************************************//** - * - * @brief mixin that provides action definition and execution - * - *****************************************************************************/ -template -class action_provider -{ -private: - //--------------------------------------------------------------- - using simple_action = std::function; - using arg_action = std::function; - using index_action = std::function; - - //----------------------------------------------------- - 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(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(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 - Derived& - set(Target& t) { - static_assert(!std::is_pointer::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 - Derived& - set(Target& t, Value&& v) { - return call(clipp::set(t, std::forward(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(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(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(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(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(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(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(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(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 - Derived& - target(T&& t, Ts&&... ts) { - target(std::forward(t)); - target(std::forward(ts)...); - return *static_cast(this); - } - - /** @brief adds action that should be called in case of a match */ - template::type>() && - (traits::is_callable() || - traits::is_callable() ) - >::type> - Derived& - target(T&& t) { - call(std::forward(t)); - return *static_cast(this); - } - - /** @brief adds object whose value should be set by command line arguments - */ - template::type>() || - (!traits::is_callable() && - !traits::is_callable() ) - >::type> - Derived& - target(T& t) { - set(t); - return *static_cast(this); - } - - //TODO remove ugly empty param list overload - Derived& - target() { - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief adds target, see member function 'target' */ - template - inline friend Derived& - operator << (Target&& t, Derived& p) { - p.target(std::forward(t)); - return p; - } - /** @brief adds target, see member function 'target' */ - template - inline friend Derived&& - operator << (Target&& t, Derived&& p) { - p.target(std::forward(t)); - return std::move(p); - } - - //----------------------------------------------------- - /** @brief adds target, see member function 'target' */ - template - inline friend Derived& - operator >> (Derived& p, Target&& t) { - p.target(std::forward(t)); - return p; - } - /** @brief adds target, see member function 'target' */ - template - inline friend Derived&& - operator >> (Derived&& p, Target&& t) { - p.target(std::forward(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 argActions_; - std::vector repeatActions_; - std::vector missingActions_; - std::vector blockedActions_; - std::vector conflictActions_; -}; - - - - - - -/*************************************************************************//** - * - * @brief mixin that provides basic common settings of parameters and groups - * - *****************************************************************************/ -template -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(this); - } - - /** @brief sets documentations string */ - Derived& doc(doc_string&& txt) { - doc_ = std::move(txt); - return *static_cast(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(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(this); - } - - -private: - //--------------------------------------------------------------- - doc_string doc_; - bool repeatable_ = false; - bool blocking_ = false; -}; - - - - -/*************************************************************************//** - * - * @brief sets documentation strings on a token - * - *****************************************************************************/ -template -inline T& -operator % (doc_string docstr, token& p) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template -inline T&& -operator % (doc_string docstr, token&& p) -{ - return std::move(p.doc(std::move(docstr))); -} - -//--------------------------------------------------------- -template -inline T& -operator % (token& p, doc_string docstr) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template -inline T&& -operator % (token&& p, doc_string docstr) -{ - return std::move(p.doc(std::move(docstr))); -} - - - - -/*************************************************************************//** - * - * @brief sets documentation strings on a token - * - *****************************************************************************/ -template -inline T& -doc(doc_string docstr, token& p) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template -inline T&& -doc(doc_string docstr, token&& 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 - none_of(arg_string str, Strings&&... strs): - excluded_{std::move(str), std::forward(strs)...} - {} - - template - none_of(const char* str, Strings&&... strs): - excluded_{arg_string(str), std::forward(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, - public detail::action_provider -{ - /** @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 - 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(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 - void - add_flags(String1&& s1, String2&& s2, Strings&&... ss) { - flags_.reserve(2 + sizeof...(ss)); - add_flags(std::forward(s1)); - add_flags(std::forward(s2), std::forward(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 -inline parameter -command(String&& flag, Strings&&... flags) -{ - return parameter{std::forward(flag), std::forward(flags)...} - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required non-blocking exact match parameter - * - *****************************************************************************/ -template -inline parameter -required(String&& flag, Strings&&... flags) -{ - return parameter{std::forward(flag), std::forward(flags)...} - .required(true).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, non-blocking exact match parameter - * - *****************************************************************************/ -template -inline parameter -option(String&& flag, Strings&&... flags) -{ - return parameter{std::forward(flag), std::forward(flags)...} - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -value(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -value(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -values(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -values(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -opt_value(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -opt_value(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -opt_values(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -opt_values(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -word(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -words(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -opt_word(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -opt_words(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -number(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -numbers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -opt_number(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -opt_numbers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -integer(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -integers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -opt_integer(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -opt_integers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes catch-all value parameter - * - *****************************************************************************/ -template -inline parameter -any_other(Targets&&... tgts) -{ - return parameter{match::any} - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes catch-all value parameter with custom filter - * - *****************************************************************************/ -template::value || - traits::is_callable::value>::type> -inline parameter -any(Filter&& filter, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .target(std::forward(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 -{ - //--------------------------------------------------------------- - /** - * @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 - 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; - using value_type = child; - -private: - using children_store = std::vector; - -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; - - //----------------------------------------------------- - 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 - 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 - explicit - group(parameter param, Params... params): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - push_back(std::move(param), std::move(params)...); - } - - template - 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::blocking() || (exclusive() && all_blocking()); - } - //----------------------------------------------------- - /** @brief determines if the entire group is blocking / positional */ - group& blocking(bool yes) { - return token::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 - group& - push_back(Param1&& param1, Param2&& param2, Params&&... params) - { - children_.reserve(children_.size() + 2 + sizeof...(params)); - push_back(std::forward(param1)); - push_back(std::forward(param2), std::forward(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 - group& - merge(group&& g1, group&& g2, Groups&&... gs) - { - merge(std::move(g1)); - merge(std::move(g2), std::forward(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 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 -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 -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 -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 -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 -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 -inline group -joinable(parameter param, Params... params) -{ - return group{std::move(param), std::move(params)...}.joinable(true); -} - -template -inline group -joinable(group p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true); -} - -template -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 -inline group -repeatable(parameter p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), - std::move(ps)...}.repeatable(true); -} - -template -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 -inline group -with_prefix(arg_string prefix, Param&& param, Params&&... params) -{ - return with_prefix(prefix, group{std::forward(param), - std::forward(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 -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), - std::forward(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 -inline group -with_suffix(arg_string suffix, Param&& param, Params&&... params) -{ - return with_suffix(suffix, group{std::forward(param), - std::forward(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 -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), - std::forward(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 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 -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 -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 -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; - using arg_mappings = std::vector; - - -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; - - -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 - 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 - 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 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 - 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 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 - 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 - 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 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 -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 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 - 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 - 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 - 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 - 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 - 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(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(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 - 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 separators; - std::stack 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 - void write(OStream& os) const - { - detail::formatting_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 - 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 - 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; - - 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 - 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 - void write(OStream& os) const { - detail::formatting_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 - void print_doc(detail::formatting_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 - void print_doc(detail::formatting_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 - void handle_spacing(detail::formatting_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 - void print_entry(detail::formatting_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
; - -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 -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 ""; -} - -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 -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 -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 -void print(OStream& os, const group& g, int level = 0); - - -/*************************************************************************//** - * - * @brief prints group or parameter; uses indentation - * - *****************************************************************************/ -template -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 -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 - diff --git a/common/external/nillable.h b/common/external/nillable.h deleted file mode 100644 index 40cf01c..0000000 --- a/common/external/nillable.h +++ /dev/null @@ -1,30 +0,0 @@ -inline constexpr struct nil_t {} nil; - -// Requirements: T is default-constructible and trivially-destructible -template -struct nillable { - bool has_value = false; - T value; - - nillable() = default; - - nillable(nil_t) : nillable() {} - nillable(const T& v) : has_value(true), value(v) {} - - nillable& operator=(nil_t) { - has_value = false; - return *this; - } - nillable& operator=(const T& v) { - value = v; - has_value = true; - return *this; - } - - const T* operator->() const { return &value; } - T* operator->() { return &value; } - - explicit operator bool() const { return has_value; } -}; - -template nillable(T)->nillable; diff --git a/common/external/tagged-union-single.h b/common/external/tagged-union-single.h deleted file mode 100644 index f0de097..0000000 --- a/common/external/tagged-union-single.h +++ /dev/null @@ -1,202 +0,0 @@ -#pragma once - -using size_t = decltype(alignof(char)); - -namespace type_traits { - -template< class T > struct remove_cv { typedef T type; }; -template< class T > struct remove_cv { typedef T type; }; -template< class T > struct remove_cv { typedef T type; }; -template< class T > struct remove_cv { typedef T type; }; -template< class T > using remove_cv_t = typename remove_cv::type; - -template< class T > struct remove_reference { typedef T type; }; -template< class T > struct remove_reference { typedef T type; }; -template< class T > struct remove_reference { typedef T type; }; -template< class T > using remove_reference_t = typename remove_reference::type; - -template< class T > -struct remove_cvref { - using type = remove_cv_t>; -}; -template< class T > using remove_cvref_t = typename remove_cvref::type; - -namespace detail { - -template struct type_identity { using type = T; }; - -template -auto try_add_lvalue_reference(int)->type_identity; -template -auto try_add_lvalue_reference(...)->type_identity; - -template -auto try_add_rvalue_reference(int)->type_identity; -template -auto try_add_rvalue_reference(...)->type_identity; - -} // type_traits::detail - -template struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference(0)) {}; -template< class T > using add_lvalue_reference_t = typename add_lvalue_reference::type; - -template struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference(0)) {}; -template< class T > using add_rvalue_reference_t = typename add_rvalue_reference::type; - -template inline constexpr bool is_same_v = false; -template inline constexpr bool is_same_v = true; -template inline constexpr bool is_void_v = is_same_v, void>; - -} // type_traits - -template type_traits::add_rvalue_reference_t declval() noexcept; - -template -inline constexpr T maxv(const T& a, const T& b) { - return (b < a) ? a : b; -} - -template -inline constexpr T minv(const T& a, const T& b) { - return (a < b) ? a : b; -} - -template -inline constexpr T clampv(const T& v, const T& lo, const T& hi) { - return minv(maxv(v, lo), hi); -} - -template -inline constexpr const T& select_ref(bool pred, const T& t, const T& f) { - return pred ? t : f; -} - -template -inline constexpr size_t max_size_of = maxv(sizeof(First), max_size_of); - -template -inline constexpr size_t max_size_of = sizeof(T); - -template -inline constexpr size_t max_align_of = maxv(alignof(First), max_align_of); - -template -inline constexpr size_t max_align_of = alignof(T); - -namespace detail { - -template -struct b1_index_of_impl { - - template - struct idx { - static constexpr size_t value = 0; - }; - - template - struct idx { - static constexpr size_t value = []() { - if constexpr (type_traits::is_same_v) { - return sizeof...(Ts) - sizeof...(Rest); - } - return idx::value; - }(); - }; -}; - -} // detail - -template -inline constexpr int base1_index_of = -detail::b1_index_of_impl::template idx::value; - -/* -Requirements: Every type is trivially-copyable and is not an array type - -Can be initialized to an empty state as if by using -std::variant -*/ -template -struct tagged_union { - - // Requirements: The return type of Visitor is default-constructible (or void) - // Returns a value-initialized object when in an empty or invalid state - template - inline constexpr auto visit(Visitor vis) { - return visit_impl(vis); - } - - template - inline constexpr auto visit(Visitor vis) const { - return visit_impl(vis); - } - - template - static constexpr int id = base1_index_of; - - int tag = 0; - - struct storage_t { - alignas(max_align_of) char bytes[max_size_of] = {}; - - template - inline constexpr T& as() { - static_assert(id != 0, "tagged_union can not hold T"); - return reinterpret_cast(bytes); - } - - template - inline constexpr const T& as() const { - static_assert(id != 0, "tagged_union can not hold T"); - return reinterpret_cast(bytes); - } - - } storage; - - constexpr tagged_union() noexcept = default; - - template - inline constexpr tagged_union(const T& val) noexcept { - tag = id; - storage.template as() = val; - } - - template - inline constexpr tagged_union& operator=(const T& val) noexcept { - tag = id; - storage.template as() = val; - return *this; - } - -private: - template - inline constexpr auto visit_impl(Visitor vis) const { - if (tag == id) { - return vis(storage.template as()); - } - if constexpr (sizeof...(TRest) > 0) { - return visit_impl(vis); - } - else { - using ReturnType = decltype(vis(declval())); - if constexpr (!type_traits::is_void_v) return ReturnType{}; - } - } - - template - inline constexpr auto visit_impl(Visitor vis) { - if (tag == id) { - return vis(storage.template as()); - } - if constexpr (sizeof...(TRest) > 0) { - return visit_impl(vis); - } - else { - using ReturnType = decltype(vis(declval())); - if constexpr (!type_traits::is_void_v) return ReturnType{}; - } - } -}; - -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...)->overloaded; diff --git a/common/rawaccel-io.hpp b/common/rawaccel-io.hpp index 7a4c59c..4050f07 100644 --- a/common/rawaccel-io.hpp +++ b/common/rawaccel-io.hpp @@ -9,6 +9,9 @@ #define RA_IOCTL CTL_CODE(0x8888, 0x888, METHOD_BUFFERED, FILE_ANY_ACCESS) +#pragma warning(push) +#pragma warning(disable:4245) // int -> DWORD conversion while passing RA_IOCTL + namespace rawaccel { mouse_modifier read() { @@ -43,6 +46,7 @@ namespace rawaccel { return mod; } + void write(mouse_modifier mod) { HANDLE ra_handle = INVALID_HANDLE_VALUE; @@ -73,3 +77,5 @@ namespace rawaccel { } } + +#pragma warning(pop) diff --git a/common/rawaccel-userspace.hpp b/common/rawaccel-userspace.hpp deleted file mode 100644 index c80262c..0000000 --- a/common/rawaccel-userspace.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include - -#include "external/clipp.h" - -#include "accel-error.hpp" -#include "rawaccel.hpp" - -namespace rawaccel { - -inline constexpr int SYSTEM_ERROR = -1; -inline constexpr int PARSE_ERROR = 1; -inline constexpr int INVALID_ARGUMENT = 2; - -template -clipp::parameter make_accel_cmd(modifier_args& args, StrFirst&& first_flag, StrRest&&... rest) { - return clipp::command(first_flag, rest...) - .set(args.acc_fn_args.accel_mode, accel_impl_t::id); -} - -mouse_modifier parse(int argc, char** argv) { - modifier_args args{}; - - auto make_opt_vec = [](vec2d& v, auto first_flag, auto... rest) { - return clipp::option(first_flag, rest...) & ( - clipp::number("xy", v.x, v.y) | ( - (clipp::required("x") & clipp::number("num", v.x)), - (clipp::required("y") & clipp::number("num", v.y)) - ) - ); - }; - - auto make_doc_fmt = [] { - return clipp::doc_formatting{} - .first_column(4) - .doc_column(28) - .last_column(80) - // min value to not split optional vec2 alternatives - .alternatives_min_split_size(5); - }; - - // default options - auto opt_sens = "sensitivity (default = 1)" % make_opt_vec(args.sens, "sens"); - - auto opt_rot = "counter-clockwise rotation (default = 0)" % ( - clipp::option("rotate") & - clipp::number("degrees", args.degrees) - ); - - // mode-independent accel options - auto opt_weight = "accel multiplier (default = 1)" % - make_opt_vec(args.acc_fn_args.acc_args.weight, "weight"); - - auto opt_offset = "speed (dots/ms) where accel kicks in (default = 0)" % ( - clipp::option("offset") & clipp::number("speed", args.acc_fn_args.acc_args.offset) - ); - - auto opt_cap = "accel scale cap (default = 9)" % - make_opt_vec(args.acc_fn_args.cap, "cap"); - - auto opt_tmin = "minimum time between polls (default = 0.4)" % ( - clipp::option("tmin") & - clipp::number("ms", args.acc_fn_args.time_min) - ); - - auto accel_var = (clipp::required("accel") & clipp::number("num", args.acc_fn_args.acc_args.accel)) % "ramp rate"; - auto limit_var = (clipp::required("limit") & clipp::number("scale", args.acc_fn_args.acc_args.limit)) % "limit"; - auto exp_var = (clipp::required("exponent") & clipp::number("num", args.acc_fn_args.acc_args.exponent)) % "exponent"; - - // modes - auto noaccel_mode = "no-accel mode" % make_accel_cmd(args, "off", "noaccel"); - - auto lin_mode = "linear accel mode:" % ( - make_accel_cmd(args, "linear"), - accel_var - ); - auto classic_mode = "classic accel mode:" % ( - make_accel_cmd(args, "classic"), - accel_var, - exp_var - ); - auto nat_mode = "natural accel mode:" % ( - make_accel_cmd(args, "natural"), - accel_var, - limit_var - ); - auto log_mode = "logarithmic accel mode:" % ( - make_accel_cmd(args, "logarithmic"), - accel_var - ); - auto sig_mode = "sigmoid accel mode:" % ( - make_accel_cmd(args, "sigmoid"), - accel_var, - limit_var, - (clipp::required("midpoint") & clipp::number("speed", args.acc_fn_args.acc_args.midpoint)) % "midpoint" - ); - auto pow_mode = "power accel mode:" % ( - make_accel_cmd(args, "power"), - exp_var, - (clipp::option("scale") & clipp::number("num", args.acc_fn_args.acc_args.power_scale)) % "scale factor" - ); - - auto accel_mode_exclusive = (lin_mode | classic_mode | nat_mode | log_mode | sig_mode | pow_mode); - auto accel_opts = "mode-independent accel options:" % (opt_cap, opt_weight, opt_offset, opt_tmin); - - bool help = false; - - auto cli = clipp::group(clipp::command("help").set(help)) | ( - noaccel_mode | (accel_mode_exclusive, accel_opts), - opt_sens, - opt_rot - ); - - if (!clipp::parse(argc, argv, cli)) { - std::cout << clipp::usage_lines(cli, "rawaccel", make_doc_fmt()); - std::exit(PARSE_ERROR); - } - - if (help) { - std::cout << clipp::make_man_page(cli, "rawaccel", make_doc_fmt()); - std::exit(0); - } - - return mouse_modifier(args); -} - -} // rawaccel diff --git a/common/rawaccel.hpp b/common/rawaccel.hpp index 59a0360..474f2aa 100644 --- a/common/rawaccel.hpp +++ b/common/rawaccel.hpp @@ -4,7 +4,7 @@ #include #include "x64-util.hpp" -#include "external/tagged-union-single.h" +#include "tagged-union-single.h" #include "accel-linear.hpp" #include "accel-classic.hpp" diff --git a/common/tagged-union-single.h b/common/tagged-union-single.h new file mode 100644 index 0000000..f0de097 --- /dev/null +++ b/common/tagged-union-single.h @@ -0,0 +1,202 @@ +#pragma once + +using size_t = decltype(alignof(char)); + +namespace type_traits { + +template< class T > struct remove_cv { typedef T type; }; +template< class T > struct remove_cv { typedef T type; }; +template< class T > struct remove_cv { typedef T type; }; +template< class T > struct remove_cv { typedef T type; }; +template< class T > using remove_cv_t = typename remove_cv::type; + +template< class T > struct remove_reference { typedef T type; }; +template< class T > struct remove_reference { typedef T type; }; +template< class T > struct remove_reference { typedef T type; }; +template< class T > using remove_reference_t = typename remove_reference::type; + +template< class T > +struct remove_cvref { + using type = remove_cv_t>; +}; +template< class T > using remove_cvref_t = typename remove_cvref::type; + +namespace detail { + +template struct type_identity { using type = T; }; + +template +auto try_add_lvalue_reference(int)->type_identity; +template +auto try_add_lvalue_reference(...)->type_identity; + +template +auto try_add_rvalue_reference(int)->type_identity; +template +auto try_add_rvalue_reference(...)->type_identity; + +} // type_traits::detail + +template struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference(0)) {}; +template< class T > using add_lvalue_reference_t = typename add_lvalue_reference::type; + +template struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference(0)) {}; +template< class T > using add_rvalue_reference_t = typename add_rvalue_reference::type; + +template inline constexpr bool is_same_v = false; +template inline constexpr bool is_same_v = true; +template inline constexpr bool is_void_v = is_same_v, void>; + +} // type_traits + +template type_traits::add_rvalue_reference_t declval() noexcept; + +template +inline constexpr T maxv(const T& a, const T& b) { + return (b < a) ? a : b; +} + +template +inline constexpr T minv(const T& a, const T& b) { + return (a < b) ? a : b; +} + +template +inline constexpr T clampv(const T& v, const T& lo, const T& hi) { + return minv(maxv(v, lo), hi); +} + +template +inline constexpr const T& select_ref(bool pred, const T& t, const T& f) { + return pred ? t : f; +} + +template +inline constexpr size_t max_size_of = maxv(sizeof(First), max_size_of); + +template +inline constexpr size_t max_size_of = sizeof(T); + +template +inline constexpr size_t max_align_of = maxv(alignof(First), max_align_of); + +template +inline constexpr size_t max_align_of = alignof(T); + +namespace detail { + +template +struct b1_index_of_impl { + + template + struct idx { + static constexpr size_t value = 0; + }; + + template + struct idx { + static constexpr size_t value = []() { + if constexpr (type_traits::is_same_v) { + return sizeof...(Ts) - sizeof...(Rest); + } + return idx::value; + }(); + }; +}; + +} // detail + +template +inline constexpr int base1_index_of = +detail::b1_index_of_impl::template idx::value; + +/* +Requirements: Every type is trivially-copyable and is not an array type + +Can be initialized to an empty state as if by using +std::variant +*/ +template +struct tagged_union { + + // Requirements: The return type of Visitor is default-constructible (or void) + // Returns a value-initialized object when in an empty or invalid state + template + inline constexpr auto visit(Visitor vis) { + return visit_impl(vis); + } + + template + inline constexpr auto visit(Visitor vis) const { + return visit_impl(vis); + } + + template + static constexpr int id = base1_index_of; + + int tag = 0; + + struct storage_t { + alignas(max_align_of) char bytes[max_size_of] = {}; + + template + inline constexpr T& as() { + static_assert(id != 0, "tagged_union can not hold T"); + return reinterpret_cast(bytes); + } + + template + inline constexpr const T& as() const { + static_assert(id != 0, "tagged_union can not hold T"); + return reinterpret_cast(bytes); + } + + } storage; + + constexpr tagged_union() noexcept = default; + + template + inline constexpr tagged_union(const T& val) noexcept { + tag = id; + storage.template as() = val; + } + + template + inline constexpr tagged_union& operator=(const T& val) noexcept { + tag = id; + storage.template as() = val; + return *this; + } + +private: + template + inline constexpr auto visit_impl(Visitor vis) const { + if (tag == id) { + return vis(storage.template as()); + } + if constexpr (sizeof...(TRest) > 0) { + return visit_impl(vis); + } + else { + using ReturnType = decltype(vis(declval())); + if constexpr (!type_traits::is_void_v) return ReturnType{}; + } + } + + template + inline constexpr auto visit_impl(Visitor vis) { + if (tag == id) { + return vis(storage.template as()); + } + if constexpr (sizeof...(TRest) > 0) { + return visit_impl(vis); + } + else { + using ReturnType = decltype(vis(declval())); + if constexpr (!type_traits::is_void_v) return ReturnType{}; + } + } +}; + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...)->overloaded; diff --git a/console/console.cpp b/console/console.cpp index 00dc481..9a1d66f 100644 --- a/console/console.cpp +++ b/console/console.cpp @@ -1,8 +1,9 @@ #include -#include #include +#include "parse.hpp" + namespace ra = rawaccel; int main(int argc, char** argv) { diff --git a/console/console.vcxproj b/console/console.vcxproj index d0ad292..0f87d94 100644 --- a/console/console.vcxproj +++ b/console/console.vcxproj @@ -91,6 +91,9 @@ + + + diff --git a/console/external/clipp.h b/console/external/clipp.h new file mode 100644 index 0000000..cca1554 --- /dev/null +++ b/console/external/clipp.h @@ -0,0 +1,7027 @@ +/***************************************************************************** + * ___ _ _ ___ ___ + * | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++ + * | |_ | |_ | | | _/ _/ version 1.2.3 + * |___||___||_| |_| |_| https://github.com/muellan/clipp + * + * Licensed under the MIT License . + * Copyright (c) 2017-2018 André Müller + * + * --------------------------------------------------------------------------- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/*************************************************************************//** + * + * @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; + + + +/*************************************************************************//** + * + * @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; +using match_function = std::function; + + + + + + +/*************************************************************************//** + * + * @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 +constexpr auto +check_is_callable(int) -> decltype( + std::declval()(std::declval()...), + std::integral_constant>::value>{} ); + +template +constexpr auto +check_is_callable(long) -> std::false_type; + +template +constexpr auto +check_is_callable_without_arg(int) -> decltype( + std::declval()(), + std::integral_constant>::value>{} ); + +template +constexpr auto +check_is_callable_without_arg(long) -> std::false_type; + + + +template +constexpr auto +check_is_void_callable(int) -> decltype( + std::declval()(std::declval()...), std::true_type{}); + +template +constexpr auto +check_is_void_callable(long) -> std::false_type; + +template +constexpr auto +check_is_void_callable_without_arg(int) -> decltype( + std::declval()(), std::true_type{}); + +template +constexpr auto +check_is_void_callable_without_arg(long) -> std::false_type; + + + +template +struct is_callable; + + +template +struct is_callable : + decltype(check_is_callable(0)) +{}; + +template +struct is_callable : + decltype(check_is_callable_without_arg(0)) +{}; + + +template +struct is_callable : + decltype(check_is_void_callable(0)) +{}; + +template +struct is_callable : + decltype(check_is_void_callable_without_arg(0)) +{}; + + + +/*************************************************************************//** + * + * @brief input range type trait + * + *****************************************************************************/ +template +constexpr auto +check_is_input_range(int) -> decltype( + begin(std::declval()), end(std::declval()), + std::true_type{}); + +template +constexpr auto +check_is_input_range(char) -> decltype( + std::begin(std::declval()), std::end(std::declval()), + std::true_type{}); + +template +constexpr auto +check_is_input_range(long) -> std::false_type; + +template +struct is_input_range : + decltype(check_is_input_range(0)) +{}; + + + +/*************************************************************************//** + * + * @brief size() member type trait + * + *****************************************************************************/ +template +constexpr auto +check_has_size_getter(int) -> + decltype(std::declval().size(), std::true_type{}); + +template +constexpr auto +check_has_size_getter(long) -> std::false_type; + +template +struct has_size_getter : + decltype(check_has_size_getter(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 sizeof(T))> +struct limits_clamped { + static T from(const V& v) { + if(v >= V(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + if(v <= V(std::numeric_limits::lowest())) { + return std::numeric_limits::lowest(); + } + return T(v); + } +}; + +template +struct limits_clamped { + static T from(const V& v) { return T(v); } +}; + + +/*************************************************************************//** + * + * @brief returns value of v as a T, clamped at T's maximum + * + *****************************************************************************/ +template +inline T clamped_on_limits(const V& v) { + return limits_clamped::from(v); +} + + + + +/*************************************************************************//** + * + * @brief type conversion helpers + * + *****************************************************************************/ +template +struct make { + static inline T from(const char* s) { + if(!s) return false; + //a conversion from const char* to / must exist + return static_cast(s); + } +}; + +template<> +struct make { + static inline bool from(const char* s) { + if(!s) return false; + return static_cast(s); + } +}; + +template<> +struct make { + static inline unsigned char from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline unsigned short int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline unsigned int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline unsigned long int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline unsigned long long int from(const char* s) { + if(!fwd_to_unsigned_int(s)) return (0); + return clamped_on_limits(std::strtoull(s,nullptr,10)); + } +}; + +template<> +struct make { + 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(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline short int from(const char* s) { + return clamped_on_limits(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline int from(const char* s) { + return clamped_on_limits(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline long int from(const char* s) { + return clamped_on_limits(std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline long long int from(const char* s) { + return (std::strtoll(s,nullptr,10)); + } +}; + +template<> +struct make { + static inline float from(const char* s) { + return (std::strtof(s,nullptr)); + } +}; + +template<> +struct make { + static inline double from(const char* s) { + return (std::strtod(s,nullptr)); + } +}; + +template<> +struct make { + static inline long double from(const char* s) { + return (std::strtold(s,nullptr)); + } +}; + +template<> +struct make { + static inline std::string from(const char* s) { + return std::string(s); + } +}; + + + +/*************************************************************************//** + * + * @brief assigns boolean constant to one or multiple target objects + * + *****************************************************************************/ +template +class assign_value +{ +public: + template + explicit constexpr + assign_value(T& target, X&& value) noexcept : + t_{std::addressof(target)}, v_{std::forward(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 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 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 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 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::from(s); + } + +private: + T* t_; +}; + + +//------------------------------------------------------------------- +/** + * @brief specialization for vectors: append element + */ +template +class map_arg_to> +{ +public: + map_arg_to(std::vector& target): t_{std::addressof(target)} {} + + void operator () (const char* s) const { + if(t_ && s) t_->push_back(detail::make::from(s)); + } + +private: + std::vector* t_; +}; + + +//------------------------------------------------------------------- +/** + * @brief specialization for bools: + * set to true regardless of string content + */ +template<> +class map_arg_to +{ +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 +T make(const arg_string& s) +{ + return detail::make::from(s); +} + + + +/*************************************************************************//** + * + * @brief removes trailing whitespace from string + * + *****************************************************************************/ +template +inline void +trimr(std::basic_string& 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 +inline void +triml(std::basic_string& 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 +inline void +trim(std::basic_string& s) +{ + triml(s); + trimr(s); +} + + +/*************************************************************************//** + * + * @brief removes all whitespaces from string + * + *****************************************************************************/ +template +inline void +remove_ws(std::basic_string& 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 +inline bool +has_prefix(const std::basic_string& subject, + const std::basic_string& 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 +inline bool +has_postfix(const std::basic_string& subject, + const std::basic_string& 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 +auto +longest_common_prefix(const InputRange& strs) + -> typename std::decay::type +{ + static_assert(traits::is_input_range(), + "parameter must satisfy the InputRange concept"); + + static_assert(traits::has_size_getter< + typename std::decay::type>(), + "elements of input range must have a ::size() member function"); + + using std::begin; + using std::end; + + using item_t = typename std::decay::type; + using str_size_t = typename std::decaysize())>::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 +subrange +longest_substring_match(const std::basic_string& arg, + const InputRange& substrings) +{ + using string_t = std::basic_string; + + static_assert(traits::is_input_range(), + "parameter must satisfy the InputRange concept"); + + static_assert(std::is_same::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 +subrange +longest_prefix_match(const std::basic_string& arg, + const InputRange& prefixes) +{ + using string_t = std::basic_string; + using s_size_t = typename string_t::size_type; + + static_assert(traits::is_input_range(), + "parameter must satisfy the InputRange concept"); + + static_assert(std::is_same::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 +inline subrange +substring_match(const std::basic_string& subject, + const std::basic_string& 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::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 +subrange +first_number_match(std::basic_string s, + C digitSeparator = C(','), + C decimalPoint = C('.'), + C exponential = C('e')) +{ + using string_t = std::basic_string; + 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; + + // overwritten to match numbers without leading 0, + // also commented out call to sanitize_args in parse + auto i = s.find_first_of("0123456789+-."); + if (i == string_t::npos) return subrange{}; + bool point = s[i] == decimalPoint; + + 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 +subrange +first_integer_match(std::basic_string s, + C digitSeparator = C(',')) +{ + using string_t = std::basic_string; + 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 +bool represents_number(const std::basic_string& 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 +bool represents_integer(const std::basic_string& 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 +inline detail::assign_value +set(T& target, V value) { + return detail::assign_value{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 +inline detail::map_arg_to +set(T& target) { + return detail::map_arg_to{target}; +} + + + +/*************************************************************************//** + * + * @brief makes function object that sets a bool to true + * + *****************************************************************************/ +inline detail::assign_value +set(bool& target) { + return detail::assign_value{target,true}; +} + +/*************************************************************************//** + * + * @brief makes function object that sets a bool to false + * + *****************************************************************************/ +inline detail::assign_value +unset(bool& target) { + return detail::assign_value{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 +inline detail::increment +increment(T& target) { + return detail::increment{target}; +} + +/*************************************************************************//** + * + * @brief makes function object that decrements using operator -- + * + *****************************************************************************/ +template +inline detail::increment_by +increment(T& target, T by) { + return detail::increment_by{target, std::move(by)}; +} + +/*************************************************************************//** + * + * @brief makes function object that increments by a fixed amount using operator += + * + *****************************************************************************/ +template +inline detail::decrement +decrement(T& target) { + return detail::decrement{target}; +} + + + + + + +/*************************************************************************//** + * + * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) + * + *****************************************************************************/ +namespace detail { + + +/*************************************************************************//** + * + * @brief mixin that provides action definition and execution + * + *****************************************************************************/ +template +class action_provider +{ +private: + //--------------------------------------------------------------- + using simple_action = std::function; + using arg_action = std::function; + using index_action = std::function; + + //----------------------------------------------------- + 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(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(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 + Derived& + set(Target& t) { + static_assert(!std::is_pointer::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 + Derived& + set(Target& t, Value&& v) { + return call(clipp::set(t, std::forward(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(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(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(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(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(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(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(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(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 + Derived& + target(T&& t, Ts&&... ts) { + target(std::forward(t)); + target(std::forward(ts)...); + return *static_cast(this); + } + + /** @brief adds action that should be called in case of a match */ + template::type>() && + (traits::is_callable() || + traits::is_callable() ) + >::type> + Derived& + target(T&& t) { + call(std::forward(t)); + return *static_cast(this); + } + + /** @brief adds object whose value should be set by command line arguments + */ + template::type>() || + (!traits::is_callable() && + !traits::is_callable() ) + >::type> + Derived& + target(T& t) { + set(t); + return *static_cast(this); + } + + //TODO remove ugly empty param list overload + Derived& + target() { + return *static_cast(this); + } + + + //--------------------------------------------------------------- + /** @brief adds target, see member function 'target' */ + template + inline friend Derived& + operator << (Target&& t, Derived& p) { + p.target(std::forward(t)); + return p; + } + /** @brief adds target, see member function 'target' */ + template + inline friend Derived&& + operator << (Target&& t, Derived&& p) { + p.target(std::forward(t)); + return std::move(p); + } + + //----------------------------------------------------- + /** @brief adds target, see member function 'target' */ + template + inline friend Derived& + operator >> (Derived& p, Target&& t) { + p.target(std::forward(t)); + return p; + } + /** @brief adds target, see member function 'target' */ + template + inline friend Derived&& + operator >> (Derived&& p, Target&& t) { + p.target(std::forward(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 argActions_; + std::vector repeatActions_; + std::vector missingActions_; + std::vector blockedActions_; + std::vector conflictActions_; +}; + + + + + + +/*************************************************************************//** + * + * @brief mixin that provides basic common settings of parameters and groups + * + *****************************************************************************/ +template +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(this); + } + + /** @brief sets documentations string */ + Derived& doc(doc_string&& txt) { + doc_ = std::move(txt); + return *static_cast(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(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(this); + } + + +private: + //--------------------------------------------------------------- + doc_string doc_; + bool repeatable_ = false; + bool blocking_ = false; +}; + + + + +/*************************************************************************//** + * + * @brief sets documentation strings on a token + * + *****************************************************************************/ +template +inline T& +operator % (doc_string docstr, token& p) +{ + return p.doc(std::move(docstr)); +} +//--------------------------------------------------------- +template +inline T&& +operator % (doc_string docstr, token&& p) +{ + return std::move(p.doc(std::move(docstr))); +} + +//--------------------------------------------------------- +template +inline T& +operator % (token& p, doc_string docstr) +{ + return p.doc(std::move(docstr)); +} +//--------------------------------------------------------- +template +inline T&& +operator % (token&& p, doc_string docstr) +{ + return std::move(p.doc(std::move(docstr))); +} + + + + +/*************************************************************************//** + * + * @brief sets documentation strings on a token + * + *****************************************************************************/ +template +inline T& +doc(doc_string docstr, token& p) +{ + return p.doc(std::move(docstr)); +} +//--------------------------------------------------------- +template +inline T&& +doc(doc_string docstr, token&& 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 + none_of(arg_string str, Strings&&... strs): + excluded_{std::move(str), std::forward(strs)...} + {} + + template + none_of(const char* str, Strings&&... strs): + excluded_{arg_string(str), std::forward(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, + public detail::action_provider +{ + /** @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 + 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(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 + void + add_flags(String1&& s1, String2&& s2, Strings&&... ss) { + flags_.reserve(2 + sizeof...(ss)); + add_flags(std::forward(s1)); + add_flags(std::forward(s2), std::forward(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 +inline parameter +command(String&& flag, Strings&&... flags) +{ + return parameter{std::forward(flag), std::forward(flags)...} + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required non-blocking exact match parameter + * + *****************************************************************************/ +template +inline parameter +required(String&& flag, Strings&&... flags) +{ + return parameter{std::forward(flag), std::forward(flags)...} + .required(true).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, non-blocking exact match parameter + * + *****************************************************************************/ +template +inline parameter +option(String&& flag, Strings&&... flags) +{ + return parameter{std::forward(flag), std::forward(flags)...} + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template +inline parameter +value(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(false); +} + +template::value || + traits::is_callable::value>::type> +inline parameter +value(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward(filter)} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template +inline parameter +values(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(true); +} + +template::value || + traits::is_callable::value>::type> +inline parameter +values(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward(filter)} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template +inline parameter +opt_value(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(false); +} + +template::value || + traits::is_callable::value>::type> +inline parameter +opt_value(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward(filter)} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any non-empty string + * + *****************************************************************************/ +template +inline parameter +opt_values(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::nonempty} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(true); +} + +template::value || + traits::is_callable::value>::type> +inline parameter +opt_values(Filter&& filter, doc_string label, Targets&&... tgts) +{ + return parameter{std::forward(filter)} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template +inline parameter +word(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template +inline parameter +words(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template +inline parameter +opt_word(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any string consisting of alphanumeric characters + * + *****************************************************************************/ +template +inline parameter +opt_words(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::alphanumeric} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template +inline parameter +number(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template +inline parameter +numbers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template +inline parameter +opt_number(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any string that represents a number + * + *****************************************************************************/ +template +inline parameter +opt_numbers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::numbers{}} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template +inline parameter +integer(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes required, blocking, repeatable value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template +inline parameter +integers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward(tgts)...) + .required(true).blocking(true).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template +inline parameter +opt_integer(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(false); +} + + + +/*************************************************************************//** + * + * @brief makes optional, blocking, repeatable value parameter; + * matches any string that represents an integer + * + *****************************************************************************/ +template +inline parameter +opt_integers(const doc_string& label, Targets&&... tgts) +{ + return parameter{match::integers{}} + .label(label) + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes catch-all value parameter + * + *****************************************************************************/ +template +inline parameter +any_other(Targets&&... tgts) +{ + return parameter{match::any} + .target(std::forward(tgts)...) + .required(false).blocking(false).repeatable(true); +} + + + +/*************************************************************************//** + * + * @brief makes catch-all value parameter with custom filter + * + *****************************************************************************/ +template::value || + traits::is_callable::value>::type> +inline parameter +any(Filter&& filter, Targets&&... tgts) +{ + return parameter{std::forward(filter)} + .target(std::forward(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 +{ + //--------------------------------------------------------------- + /** + * @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 + 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; + using value_type = child; + +private: + using children_store = std::vector; + +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; + + //----------------------------------------------------- + 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 + 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 + explicit + group(parameter param, Params... params): + children_{}, exclusive_{false}, joinable_{false}, scoped_{true} + { + push_back(std::move(param), std::move(params)...); + } + + template + 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::blocking() || (exclusive() && all_blocking()); + } + //----------------------------------------------------- + /** @brief determines if the entire group is blocking / positional */ + group& blocking(bool yes) { + return token::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 + group& + push_back(Param1&& param1, Param2&& param2, Params&&... params) + { + children_.reserve(children_.size() + 2 + sizeof...(params)); + push_back(std::forward(param1)); + push_back(std::forward(param2), std::forward(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 + group& + merge(group&& g1, group&& g2, Groups&&... gs) + { + merge(std::move(g1)); + merge(std::move(g2), std::forward(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 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 +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 +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 +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 +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 +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 +inline group +joinable(parameter param, Params... params) +{ + return group{std::move(param), std::move(params)...}.joinable(true); +} + +template +inline group +joinable(group p1, P2 p2, Ps... ps) +{ + return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true); +} + +template +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 +inline group +repeatable(parameter p1, P2 p2, Ps... ps) +{ + return group{std::move(p1), std::move(p2), + std::move(ps)...}.repeatable(true); +} + +template +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 +inline group +with_prefix(arg_string prefix, Param&& param, Params&&... params) +{ + return with_prefix(prefix, group{std::forward(param), + std::forward(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 +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), + std::forward(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 +inline group +with_suffix(arg_string suffix, Param&& param, Params&&... params) +{ + return with_suffix(suffix, group{std::forward(param), + std::forward(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 +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), + std::forward(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 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 +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 +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 +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; + using arg_mappings = std::vector; + + +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; + + +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 + 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 + 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 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 + 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 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 + 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 + 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 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 +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 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 + 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 + 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 + 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 + 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 + 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(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(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 + 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 separators; + std::stack 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 + void write(OStream& os) const + { + detail::formatting_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 + 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 + 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; + + 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 + 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 + void write(OStream& os) const { + detail::formatting_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 + void print_doc(detail::formatting_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 + void print_doc(detail::formatting_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 + void handle_spacing(detail::formatting_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 + void print_entry(detail::formatting_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
; + +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 +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 ""; +} + +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 +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 +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 +void print(OStream& os, const group& g, int level = 0); + + +/*************************************************************************//** + * + * @brief prints group or parameter; uses indentation + * + *****************************************************************************/ +template +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 +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 + diff --git a/console/parse.hpp b/console/parse.hpp new file mode 100644 index 0000000..1cdb3fb --- /dev/null +++ b/console/parse.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include +#include + +#include "external/clipp.h" + +namespace rawaccel { + + inline constexpr int SYSTEM_ERROR = -1; + inline constexpr int PARSE_ERROR = 1; + inline constexpr int INVALID_ARGUMENT = 2; + + template + clipp::parameter make_accel_cmd(modifier_args& args, StrFirst&& first_flag, StrRest&&... rest) { + return clipp::command(first_flag, rest...) + .set(args.acc_fn_args.accel_mode, accel_impl_t::id); + } + + mouse_modifier parse(int argc, char** argv) { + modifier_args args{}; + + auto make_opt_vec = [](vec2d& v, auto first_flag, auto... rest) { + return clipp::option(first_flag, rest...) & ( + clipp::number("xy", v.x, v.y) | ( + (clipp::required("x") & clipp::number("num", v.x)), + (clipp::required("y") & clipp::number("num", v.y)) + ) + ); + }; + + auto make_doc_fmt = [] { + return clipp::doc_formatting{} + .first_column(4) + .doc_column(28) + .last_column(80) + // min value to not split optional vec2 alternatives + .alternatives_min_split_size(5); + }; + + // default options + auto opt_sens = "sensitivity (default = 1)" % make_opt_vec(args.sens, "sens"); + + auto opt_rot = "counter-clockwise rotation (default = 0)" % ( + clipp::option("rotate") & + clipp::number("degrees", args.degrees) + ); + + // mode-independent accel options + auto opt_weight = "accel multiplier (default = 1)" % + make_opt_vec(args.acc_fn_args.acc_args.weight, "weight"); + + auto opt_offset = "speed (dots/ms) where accel kicks in (default = 0)" % ( + clipp::option("offset") & clipp::number("speed", args.acc_fn_args.acc_args.offset) + ); + + auto opt_cap = "accel scale cap (default = 9)" % + make_opt_vec(args.acc_fn_args.cap, "cap"); + + auto opt_tmin = "minimum time between polls (default = 0.4)" % ( + clipp::option("tmin") & + clipp::number("ms", args.acc_fn_args.time_min) + ); + + auto accel_var = (clipp::required("accel") & clipp::number("num", args.acc_fn_args.acc_args.accel)) % "ramp rate"; + auto limit_var = (clipp::required("limit") & clipp::number("scale", args.acc_fn_args.acc_args.limit)) % "limit"; + auto exp_var = (clipp::required("exponent") & clipp::number("num", args.acc_fn_args.acc_args.exponent)) % "exponent"; + + // modes + auto noaccel_mode = "no-accel mode" % make_accel_cmd(args, "off", "noaccel"); + + auto lin_mode = "linear accel mode:" % ( + make_accel_cmd(args, "linear"), + accel_var + ); + auto classic_mode = "classic accel mode:" % ( + make_accel_cmd(args, "classic"), + accel_var, + exp_var + ); + auto nat_mode = "natural accel mode:" % ( + make_accel_cmd(args, "natural"), + accel_var, + limit_var + ); + auto log_mode = "logarithmic accel mode:" % ( + make_accel_cmd(args, "logarithmic"), + accel_var + ); + auto sig_mode = "sigmoid accel mode:" % ( + make_accel_cmd(args, "sigmoid"), + accel_var, + limit_var, + (clipp::required("midpoint") & clipp::number("speed", args.acc_fn_args.acc_args.midpoint)) % "midpoint" + ); + auto pow_mode = "power accel mode:" % ( + make_accel_cmd(args, "power"), + exp_var, + (clipp::option("scale") & clipp::number("num", args.acc_fn_args.acc_args.power_scale)) % "scale factor" + ); + + auto accel_mode_exclusive = (lin_mode | classic_mode | nat_mode | log_mode | sig_mode | pow_mode); + auto accel_opts = "mode-independent accel options:" % (opt_cap, opt_weight, opt_offset, opt_tmin); + + bool help = false; + + auto cli = clipp::group(clipp::command("help").set(help)) | ( + noaccel_mode | (accel_mode_exclusive, accel_opts), + opt_sens, + opt_rot + ); + + if (!clipp::parse(argc, argv, cli)) { + std::cout << clipp::usage_lines(cli, "rawaccel", make_doc_fmt()); + std::exit(PARSE_ERROR); + } + + if (help) { + std::cout << clipp::make_man_page(cli, "rawaccel", make_doc_fmt()); + std::exit(0); + } + + return mouse_modifier(args); + } + +} // rawaccel diff --git a/rawaccel.sln b/rawaccel.sln index 0db5ecb..3325e47 100644 --- a/rawaccel.sln +++ b/rawaccel.sln @@ -23,9 +23,9 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution common-install\common-install.vcxitems*{058d66c6-d88b-4fdb-b0e4-0a6fe7483b95}*SharedItemsImports = 9 common\common.vcxitems*{24b4226f-1461-408f-a1a4-1371c97153ea}*SharedItemsImports = 9 - common\common.vcxitems*{28a3656f-a1de-405c-b547-191c32ec555f}*SharedItemsImports = 4 common-install\common-install.vcxitems*{896950d1-520a-420a-b6b1-73014b92a68c}*SharedItemsImports = 4 common-install\common-install.vcxitems*{a4097ff6-a6f0-44e8-b8d0-538d0fb75936}*SharedItemsImports = 4 + common\common.vcxitems*{ab7b3759-b85f-4067-8935-fb4539b41869}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU -- cgit v1.2.3