From 9816bc02c588bebca65be68a1d53bf26055e64e6 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 3 Dec 2025 15:43:08 -0800 Subject: [PATCH 001/157] Makefile: removing Makefile --- Makefile | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index a92b07a..0000000 --- a/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2011-2020 University of Southern California and -# Andrew D. Smith and Timothy Daley -# -# Authors: Timothy Daley and Andrew D. Smith -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -ifndef install_dir -install_dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -endif - -all: - @make -C src - -install: - @make -C src install_dir=$(install_dir) install - -clean: - @make -C src clean - -distclean: clean - @rm -rf $(install_dir)/bin - -.PHONY: all distclean clean install From 105e045ba031a2541f4abbe017760397db5d5391 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 3 Dec 2025 16:33:15 -0800 Subject: [PATCH 002/157] src/bamxx: adding bamxx submodule to work with BAM files --- src/bamxx | 1 + 1 file changed, 1 insertion(+) create mode 160000 src/bamxx diff --git a/src/bamxx b/src/bamxx new file mode 160000 index 0000000..684f4cd --- /dev/null +++ b/src/bamxx @@ -0,0 +1 @@ +Subproject commit 684f4cd6956b384632a4b4778ffc57d256c46a9a From a335fab64364d0dc95d55790eb856579c810df09 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 3 Dec 2025 16:33:28 -0800 Subject: [PATCH 003/157] .gitmodules: udpating submodule --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index ee3d393..4b17d94 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "src/smithlab_cpp"] path = src/smithlab_cpp url = ../smithlab_cpp.git +[submodule "src/bamxx"] + path = src/bamxx + url = https://github.com/smithlabcode/bamxx From b8d60527b1663ca161959319ab1d500f0bb15c03 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:04:48 -0700 Subject: [PATCH 004/157] Makefile.am: removing the to-mr program, as this functionality will move into just parsing BAM files --- Makefile.am | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 9629846..11239b2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,7 +27,6 @@ LDADD = src/smithlab_cpp/libsmithlab_cpp.a bin_PROGRAMS = preseq if ENABLE_HTS -bin_PROGRAMS += to-mr AM_CPPFLAGS += -DHAVE_HTSLIB endif @@ -39,6 +38,3 @@ preseq_SOURCES = \ src/load_data_for_complexity.cpp \ src/moment_sequence.hpp \ src/moment_sequence.cpp - -to_mr_SOURCES = \ - src/to-mr.cpp From c26d81455b2fa9e33d207fa6975401f73e6b8619 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:05:09 -0700 Subject: [PATCH 005/157] configure.ac: bumping to c++23 --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 8060b7a..955ffc9 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl This file is part of preseq dnl -dnl Copyright (C) 2018-2022: Andrew D. Smith +dnl Copyright (C) 2018-2025: Andrew D. Smith dnl dnl Authors: Andrew D. Smith dnl @@ -24,7 +24,7 @@ AM_INIT_AUTOMAKE([subdir-objects foreign]) AC_CONFIG_MACRO_DIR([m4]) AC_LANG(C++) AC_PROG_CXX -AX_CXX_COMPILE_STDCXX_11([noext], [mandatory]) +AX_CXX_COMPILE_STDCXX(23, [noext], [mandatory]) AC_PROG_RANLIB dnl recursively configure smithlab_cpp From aea5a8afa7310a17345155f0c34150d02edc0074 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:05:40 -0700 Subject: [PATCH 006/157] m4/ax_cxx_compile_stdcxx.m4: updating to allow for checking for c++23 --- m4/ax_cxx_compile_stdcxx.m4 | 153 ++++++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 17 deletions(-) diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 index 43087b2..fe6ae17 100644 --- a/m4/ax_cxx_compile_stdcxx.m4 +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -10,13 +10,13 @@ # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and -# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) -# or '14' (for the C++14 standard). +# CXXCPP to enable support. VERSION may be '11', '14', '17', '20', or +# '23' for the respective C++ standard version. # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with -# preference for an extended mode. +# preference for no added switch, and then for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is @@ -35,13 +35,16 @@ # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021, 2024 Jörn Heusipp +# Copyright (c) 2015, 2022, 2023, 2024 Olly Betts # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 11 +#serial 25 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). @@ -50,6 +53,8 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], + [$1], [23], [ax_cxx_compile_alternatives="23"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], @@ -62,6 +67,16 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl AC_LANG_PUSH([C++])dnl ac_success=no + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do @@ -91,9 +106,18 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" + dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) for alternative in ${ax_cxx_compile_alternatives}; do - for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do + if test x"$switch" = xMSVC; then + dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide + dnl with -std=c++17. We suffix the cache variable name with _MSVC to + dnl avoid this. + switch=-std:c++${alternative} + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) + else + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + fi AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" @@ -137,23 +161,44 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl dnl Test body for checking C++11 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], - _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11] ) - dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], - _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 - _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14] ) +dnl Test body for checking C++17 support + m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], - _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 - _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 - _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17] +) + +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20] ) +dnl Test body for checking C++23 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_23], + [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_23] +) + + dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ @@ -165,7 +210,21 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201103L +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +// +// The value __cplusplus ought to have is available in _MSVC_LANG since +// Visual Studio 2015 Update 3: +// +// https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros +// +// This was also the first MSVC version to support C++14 so we can't use the +// value of either __cplusplus or _MSVC_LANG to quickly rule out MSVC having +// C++11 or C++14 support, but we can check _MSVC_LANG for C++17 and later. +#elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" @@ -456,7 +515,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201402L +#elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" @@ -580,7 +639,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201703L +#elif (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 201703L #error "This is not a C++17 compiler" @@ -946,6 +1005,66 @@ namespace cxx17 } // namespace cxx17 -#endif // __cplusplus < 201703L +#endif // (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 201703L + +]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202002L + +#error "This is not a C++20 compiler" + +#else + +#include + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202002L + +]]) + + +dnl Tests for new features in C++23 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_23], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202302L + +#error "This is not a C++23 compiler" + +#else + +#include + +namespace cxx23 +{ + +// As C++23 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx23 + +#endif // (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202302L ]]) From 1d7e53f1dbdf5e6abe9d334a39e67944fe35bf2d Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:21:35 -0700 Subject: [PATCH 007/157] src/continued_fraction.hcpp: modernization and formatting --- src/continued_fraction.cpp | 362 +++++++++++++++++++------------------ src/continued_fraction.hpp | 116 +++++++----- 2 files changed, 255 insertions(+), 223 deletions(-) diff --git a/src/continued_fraction.cpp b/src/continued_fraction.cpp index 2a10dfb..bcec8a3 100644 --- a/src/continued_fraction.cpp +++ b/src/continued_fraction.cpp @@ -1,33 +1,30 @@ -/* Copyright (C) 2013 University of Southern California and - * Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 University of Southern California and + * Andrew D. Smith and Timothy Daley * - * Authors: Andrew D. Smith and Timothy Daley + * Authors: Andrew D. Smith and Timothy Daley * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #include "continued_fraction.hpp" -#include -#include +#include #include - -using std::vector; -using std::min; -using std::isfinite; -using std::pow; -using std::fabs; +#include +#include +#include +#include // ADS: the std::pow function is used frequently to get (-1)^x for // integer x. This doesn't make sense, and should be replaced at some @@ -42,53 +39,55 @@ using std::fabs; * for the a0/(1 - a1x/(1 - a2x/... see https://dlmf.nist.gov/3.10 */ static void -quotdiff_algorithm(const vector &ps_coeffs, vector &cf_coeffs) { - - const size_t depth = ps_coeffs.size(); // degree of power series +quotdiff_algorithm(const std::vector &ps_coeffs, + std::vector &cf_coeffs) { + const std::size_t depth = std::size(ps_coeffs); // degree of power series // q_table[0] never used, and undefined - vector > q_table(depth, vector(depth + 1, 0.0)); + std::vector> q_table(depth, + std::vector(depth + 1, 0.0)); // q_table[1][j] = ratio of ps coefficients - for (size_t j = 0; j < depth - 1; j++) - q_table[1][j] = ps_coeffs[j + 1]/ps_coeffs[j]; + for (std::size_t j = 0; j < depth - 1; ++j) + q_table[1][j] = ps_coeffs[j + 1] / ps_coeffs[j]; // e_table[0] is always 0 - vector > e_table(depth, vector(depth + 1, 0.0)); + std::vector> e_table(depth, + std::vector(depth + 1, 0.0)); // e_table[1] follows the general recurrence (same as in loop below) - for (size_t j = 0; j < depth - 1; j++) - e_table[1][j] = q_table[1][j+1] - q_table[1][j] + e_table[0][j+1]; + for (std::size_t j = 0; j < depth - 1; ++j) + e_table[1][j] = q_table[1][j + 1] - q_table[1][j] + e_table[0][j + 1]; // using intial values of E(i)(j)'s and Q(i)(j)'s, fill rest of the // q table and e table - for (size_t i = 2; i < depth; i++) { - - for (size_t j = 0; j < depth; j++) - q_table[i][j] = q_table[i-1][j+1]*e_table[i-1][j+1]/e_table[i-1][j]; + for (std::size_t i = 2; i < depth; ++i) { + for (std::size_t j = 0; j < depth; ++j) + q_table[i][j] = + q_table[i - 1][j + 1] * e_table[i - 1][j + 1] / e_table[i - 1][j]; - for (size_t j = 0; j < depth; j++) - e_table[i][j] = q_table[i][j+1] - q_table[i][j] + e_table[i-1][j+1]; + for (std::size_t j = 0; j < depth; ++j) + e_table[i][j] = q_table[i][j + 1] - q_table[i][j] + e_table[i - 1][j + 1]; } cf_coeffs.resize(depth); // first CT coefficient is first PS coefficient cf_coeffs[0] = ps_coeffs[0]; // set remaining CF coefficients from e and q table values - for (size_t i = 1; i < depth; ++i) - cf_coeffs[i] = (i % 2 == 0) ? -e_table[i/2][0] : -q_table[(i + 1)/2][0]; + for (std::size_t i = 1; i < depth; ++i) + cf_coeffs[i] = (i % 2 == 0) ? -e_table[i / 2][0] : -q_table[(i + 1) / 2][0]; } - /* compute CF coeffs when upper_offset > 0 above the diagonal; this * means degree of polynomial in numerator of Pade approximant is * greater than degree of polynomial in the denominator */ static void -quotdiff_above_diagonal(const vector &ps_coeffs, const size_t offset, - vector &cf_coeffs, - vector &offset_coeffs) { - +quotdiff_above_diagonal(const std::vector &ps_coeffs, + const std::size_t offset, + std::vector &cf_coeffs, + std::vector &offset_coeffs) { // get the high order PS coeffs for approximation by CF - vector high_ps_coeffs(begin(ps_coeffs) + offset, end(ps_coeffs)); + std::vector high_ps_coeffs(std::cbegin(ps_coeffs) + offset, + std::cend(ps_coeffs)); // use QD algorithm to determine CF coefficients quotdiff_algorithm(high_ps_coeffs, cf_coeffs); @@ -98,28 +97,26 @@ quotdiff_above_diagonal(const vector &ps_coeffs, const size_t offset, offset_coeffs.resize(offset); } - // calculate CF coeffs when lower_offset > 0 static void -quotdiff_below_diagonal(const vector &ps_coeffs, const size_t offset, - vector &cf_coeffs, - vector &offset_coeffs) { - +quotdiff_below_diagonal(const std::vector &ps_coeffs, + const std::size_t offset, + std::vector &cf_coeffs, + std::vector &offset_coeffs) { // need to work with reciprocal series g = 1/f, then invert - vector recip_ps_coeffs(ps_coeffs.size()); - recip_ps_coeffs[0] = 1.0/ps_coeffs[0]; - for (size_t i = 1; i < ps_coeffs.size(); ++i) { - + std::vector recip_ps_coeffs(std::size(ps_coeffs)); + recip_ps_coeffs[0] = 1.0 / ps_coeffs[0]; + for (std::size_t i = 1; i < std::size(ps_coeffs); ++i) { double x = 0.0; - for (size_t j = 0; j < i; ++j) - x += ps_coeffs[i - j]*recip_ps_coeffs[j]; + for (std::size_t j = 0; j < i; ++j) + x += ps_coeffs[i - j] * recip_ps_coeffs[j]; - recip_ps_coeffs[i] = -x/ps_coeffs[0]; + recip_ps_coeffs[i] = -x / ps_coeffs[0]; } // qd to compute cf_coeffs using remaining coeffs - vector high_recip_ps_coeffs(begin(recip_ps_coeffs) + offset, - end(recip_ps_coeffs)); + std::vector high_recip_ps_coeffs( + std::cbegin(recip_ps_coeffs) + offset, std::cend(recip_ps_coeffs)); quotdiff_algorithm(high_recip_ps_coeffs, cf_coeffs); // set offset coeffs to 1st "offset" PS coeffs of 1/f (reciprocal) @@ -127,144 +124,142 @@ quotdiff_below_diagonal(const vector &ps_coeffs, const size_t offset, offset_coeffs.resize(offset); } - /* decrease degree of CF keeping coeffs equal to original */ void -decrease_degree(const size_t decrement, ContinuedFraction &the_cf) { - assert(decrement < the_cf.degree); - the_cf.ps_coeffs.resize(the_cf.ps_coeffs.size() - decrement); - the_cf.cf_coeffs.resize(the_cf.cf_coeffs.size() - decrement); - the_cf.degree -= decrement; +decrease_degree(const std::size_t decrement, ContinuedFraction &cf) { + assert(decrement < cf.degree); + cf.ps_coeffs.resize(std::size(cf.ps_coeffs) - decrement); + cf.cf_coeffs.resize(std::size(cf.cf_coeffs) - decrement); + cf.degree -= decrement; } - void -truncate_degree(const size_t n_terms, ContinuedFraction &the_cf) { - if (the_cf.degree < n_terms) { - the_cf = ContinuedFraction(); +truncate_degree(const std::size_t n_terms, ContinuedFraction &cf) { + if (cf.degree < n_terms) { + cf = ContinuedFraction(); } else { - the_cf.ps_coeffs.resize(n_terms); - the_cf.cf_coeffs.resize(n_terms - the_cf.offset_coeffs.size()); - the_cf.degree = n_terms; + cf.ps_coeffs.resize(n_terms); + cf.cf_coeffs.resize(n_terms - std::size(cf.offset_coeffs)); + cf.degree = n_terms; } } - -ContinuedFraction::ContinuedFraction(const vector &ps_cf, - const int di, const size_t dg) : +ContinuedFraction::ContinuedFraction(const std::vector &ps_cf, + const int di, const std::size_t dg) : ps_coeffs(ps_cf), diagonal_idx(di), degree(dg) { if (diagonal_idx == 0) quotdiff_algorithm(ps_coeffs, cf_coeffs); else if (diagonal_idx > 0) quotdiff_above_diagonal(ps_coeffs, diagonal_idx, cf_coeffs, offset_coeffs); - else // if (cont_frac_estimate.lower_offset > 0) { + else // if (cont_frac_estimate.lower_offset > 0) { quotdiff_below_diagonal(ps_coeffs, -diagonal_idx, cf_coeffs, offset_coeffs); // NOTE: negative sign "-" (-diagonal_idx > 0) for below diagonal } -//////////////////////////////////////////////////////////////////////// -//// FUNCTIONS TO EVALUATE CONTINUED FRACTIONS AT A POINT +ContinuedFraction::ContinuedFraction(const std::vector &hist, + const std::size_t max_terms) : + degree{max_terms} { + for (std::size_t j = 1; j <= max_terms; ++j) + ps_coeffs.push_back(hist[j] * std::pow(-1.0, j + 1)); + quotdiff_algorithm(ps_coeffs, cf_coeffs); +} + +/// Functions to evaluate continued fractions at a point -static double -get_rescale_value(const double numerator, const double denominator) { - static const double tolerance = 1e-20; // magic - const double rescale_val = fabs(numerator) + fabs(denominator); - if (rescale_val > 1.0/tolerance) - return 1.0/rescale_val; +[[nodiscard]] static auto +get_rescale_value(const double numerator, const double denominator) -> double { + static const double tolerance = 1e-20; // magic + const double rescale_val = std::fabs(numerator) + std::fabs(denominator); + if (rescale_val > 1.0 / tolerance) + return 1.0 / rescale_val; else if (rescale_val < tolerance) - return 1.0/rescale_val; + return 1.0 / rescale_val; return 1.0; } - /* calculate ContinuedFraction approx when there is no offset uses euler's * recursion */ -static double -evaluate_on_diagonal(const vector &cf_coeffs, - const double val, const size_t depth) { - +[[nodiscard]] static auto +evaluate_on_diagonal(const std::vector &cf_coeffs, const double val, + const std::size_t depth) -> double { // initialize - double current_num = 0.0; - double prev_num1 = cf_coeffs[0]; - double prev_num2 = 0.0; + double current_numer = 0.0; + double prev_numer1 = cf_coeffs[0]; + double prev_numer2 = 0.0; double current_denom = 0.0; double prev_denom1 = 1.0; double prev_denom2 = 1.0; - for (size_t i = 1; i < min(cf_coeffs.size(), depth); i++) { - + const auto lim = std::min(std::size(cf_coeffs), depth); + for (std::size_t i = 1; i < lim; ++i) { // calculate current values - current_num = prev_num1 + cf_coeffs[i]*val*prev_num2; - current_denom = prev_denom1 + cf_coeffs[i]*val*prev_denom2; + current_numer = prev_numer1 + cf_coeffs[i] * val * prev_numer2; + current_denom = prev_denom1 + cf_coeffs[i] * val * prev_denom2; // update previous values - prev_num2 = prev_num1; - prev_num1 = current_num; + prev_numer2 = prev_numer1; + prev_numer1 = current_numer; prev_denom2 = prev_denom1; prev_denom1 = current_denom; // now rescale all values - const double rescale_val = get_rescale_value(current_num, current_denom); + const double rescale_val = get_rescale_value(current_numer, current_denom); - current_num *= rescale_val; + current_numer *= rescale_val; current_denom *= rescale_val; - prev_num1 *= rescale_val; - prev_num2 *= rescale_val; + prev_numer1 *= rescale_val; + prev_numer2 *= rescale_val; prev_denom1 *= rescale_val; prev_denom2 *= rescale_val; } - return current_num/current_denom; + return current_numer / current_denom; } -static double -evaluate_power_series(const vector &ps_coeffs, const double val) { +[[nodiscard]] static auto +evaluate_power_series(const std::vector &ps_coeffs, + const double val) -> double { double x = 0.0; - for (size_t i = 0; i < ps_coeffs.size(); i++) - x += ps_coeffs[i]*pow(val, i); + for (std::size_t i = 0; i < std::size(ps_coeffs); ++i) + x += ps_coeffs[i] * std::pow(val, i); return x; } - /* evaluate CF when upper_offset > 0 using Euler's recursion */ -static double -evaluate_above_diagonal(const vector &cf_coeffs, - const vector &offset_coeffs, - const double val, const size_t depth) { - +[[nodiscard]] static auto +evaluate_above_diagonal(const std::vector &cf_coeffs, + const std::vector &offset_coeffs, + const double val, const std::size_t depth) -> double { const double cf_part = - evaluate_on_diagonal(cf_coeffs, val, depth - offset_coeffs.size()); + evaluate_on_diagonal(cf_coeffs, val, depth - std::size(offset_coeffs)); const double ps_part = evaluate_power_series(offset_coeffs, val); - return ps_part + pow(val, offset_coeffs.size())*cf_part; + return ps_part + std::pow(val, std::size(offset_coeffs)) * cf_part; } - // calculate ContinuedFraction approx when lower_offdiag > 0 -static double -evaluate_below_diagonal(const vector &cf_coeffs, - const vector &offset_coeffs, - const double val, const size_t depth) { - +[[nodiscard]] static auto +evaluate_below_diagonal(const std::vector &cf_coeffs, + const std::vector &offset_coeffs, + const double val, const std::size_t depth) -> double { const double cf_part = - evaluate_on_diagonal(cf_coeffs, val, depth - offset_coeffs.size()); + evaluate_on_diagonal(cf_coeffs, val, depth - std::size(offset_coeffs)); const double ps_part = evaluate_power_series(offset_coeffs, val); // recall that if lower_offset > 0, we are working with 1/f, invert approx - return 1.0/(ps_part + pow(val, offset_coeffs.size())*cf_part); + return 1.0 / (ps_part + std::pow(val, std::size(offset_coeffs)) * cf_part); } - // evaluate CF at a given point -double -ContinuedFraction::operator()(const double val) const { +[[nodiscard]] auto +ContinuedFraction::evaluate(const double val) const -> double { if (diagonal_idx > 0) return evaluate_above_diagonal(cf_coeffs, offset_coeffs, val, degree); else if (diagonal_idx < 0) @@ -273,111 +268,124 @@ ContinuedFraction::operator()(const double val) const { return evaluate_on_diagonal(cf_coeffs, val, degree); } +void +ContinuedFraction::extrapolate_curve(const double initial_distinct, + const double n_observations, + const double initial_sample_size, + const double step_size, + const double max_sample_size, + std::vector &estimates) const { + double current_sample_size = initial_sample_size; + while (current_sample_size < max_sample_size) { + const double fold = (current_sample_size - n_observations) / n_observations; + assert(fold >= 0.0); + estimates.push_back(initial_distinct + fold * evaluate(fold)); + current_sample_size += step_size; + } +} -std::ostream& -operator<<(std::ostream& the_stream, const ContinuedFraction &cf) { - using std::ios_base; - using std::setw; - - ios_base::fmtflags orig_flags = the_stream.flags(); - the_stream.setf(ios_base::fixed, ios_base::floatfield); +auto +operator<<(std::ostream &the_stream, + const ContinuedFraction &cf) -> std::ostream & { + std::ios_base::fmtflags orig_flags = the_stream.flags(); + the_stream.setf(std::ios_base::fixed, std::ios_base::floatfield); the_stream.precision(2); the_stream << "OFFSET_COEFFS" << '\t' << "PS_COEFFS" << '\n'; - const size_t offset = cf.offset_coeffs.size(); - for (size_t i = 0; i < offset; ++i) - the_stream << setw(12) << cf.offset_coeffs[i] << '\t' - << setw(12) << cf.ps_coeffs[i] << '\n'; + const std::size_t offset = std::size(cf.offset_coeffs); + for (std::size_t i = 0; i < offset; ++i) + the_stream << std::setw(12) << cf.offset_coeffs[i] << '\t' << std::setw(12) + << cf.ps_coeffs[i] << '\n'; the_stream << "CF_COEFFS" << '\n'; - for (size_t i = 0; i < cf.cf_coeffs.size(); ++i) - the_stream << setw(12) << cf.cf_coeffs[i] << '\t' - << setw(12) << cf.ps_coeffs[i + offset] << '\n'; + for (std::size_t i = 0; i < std::size(cf.cf_coeffs); ++i) + the_stream << std::setw(12) << cf.cf_coeffs[i] << '\t' << std::setw(12) + << cf.ps_coeffs[i + offset] << '\n'; the_stream.flags(orig_flags); return the_stream; } - // estimate yields by evaluating the CF at given points void ContinuedFraction::extrapolate_distinct(const double max_value, const double step_size, - vector &estimates) const { + std::vector &estimates) const { estimates.clear(); estimates.push_back(0); for (double t = step_size; t <= max_value; t += step_size) - estimates.push_back(t*operator()(t)); + estimates.push_back(t * operator()(t)); } -//////////////////////////////////////////////////////////////////////// -//////////////// CONTINUED FRACTION APPROXIMATION CLASS BELOW - -typedef ContinuedFractionApproximation CFA; +/// Continued fraction _approximation_ -const size_t CFA::min_allowed_degree = 4; +using CFA = ContinuedFractionApproximation; +const std::size_t CFA::min_allowed_degree = 4; const double CFA::search_max_val = 100; const double CFA::search_step_size = 0.05; -/* check if a sequence of estimates are "stable": in [0, infty, - * increasing, negative 2nd deriv +/* check if a sequence of estimates are "stable": in [0, infty), increasing, + * negative 2nd deriv */ -bool -check_yield_estimates_stability(const vector &estimates) { - +[[nodiscard]] auto +check_yield_estimates_stability(const std::vector &estimates) -> bool { // require estimates are non-negative and finite - for (size_t i = 0; i < estimates.size(); ++i) - if (!std::isfinite(estimates[i]) || estimates[i] < 0.0) + for (const auto estimate : estimates) + if (!std::isfinite(estimate) || estimate < 0.0) return false; // require estimate to be increasing - for (size_t i = 1; i < estimates.size(); ++i) + for (std::size_t i = 1; i < std::size(estimates); ++i) if (estimates[i] < estimates[i - 1]) return false; // require negative second derivative - for (size_t i = 2; i < estimates.size(); ++i) - if (estimates[i-1] - estimates[i-2] < estimates[i] - estimates[i-1]) + const auto neg_2nd_deriv = [](const auto x1, const auto x2, const auto x3) { + return (x2 - x1) < (x3 - x2); + }; + for (std::size_t i = 2; i < std::size(estimates); ++i) + if (neg_2nd_deriv(estimates[i - 2], estimates[i - 1], estimates[i])) return false; return !estimates.empty(); } -/* Finds the optimal number of terms (i.e. degree, depth, etc.) of the - * continued fraction by checking for stability of estimates at - * specific points for yield. New way for searching for optimal CF +/* Finds the optimal number of terms (i.e. degree, depth, etc.) of the continued + * fraction by checking for stability of estimates at specific points for + * yield. New way for searching for optimal CF. */ -ContinuedFraction -CFA::optimal_cont_frac_distinct(const vector &counts_hist) const { +[[nodiscard]] auto +ContinuedFractionApproximation::optimal_cf_distinct( + const std::vector &counts_hist) const -> ContinuedFraction { // we expect to use an underestimate, but this is dealt with outside // by ensuring we have an even number of max terms - if (max_terms >= counts_hist.size()) - return ContinuedFraction(); + if (max_terms >= std::size(counts_hist)) + return {}; - vector ps_coeffs; - for (size_t j = 1; j <= max_terms; j++) - ps_coeffs.push_back(counts_hist[j]*pow(-1.0, j + 1)); + std::vector ps_coeffs; + for (std::size_t j = 1; j <= max_terms; ++j) + ps_coeffs.push_back(counts_hist[j] * std::pow(-1.0, j + 1)); ContinuedFraction full_cf(ps_coeffs, diagonal_idx, max_terms); // if max terms in {3,4,5,6}, check only that degree if (max_terms >= 3 && max_terms <= 6) { - vector estimates; + std::vector estimates; full_cf.extrapolate_distinct(search_max_val, search_step_size, estimates); if (check_yield_estimates_stability(estimates)) return full_cf; } else { - // if max terms >= 7, start at 7 and check increasing cont frac's - for (size_t i = 7 + (max_terms % 2 == 0); i <= max_terms; i += 2) { - ContinuedFraction trunc_cf(full_cf); - truncate_degree(i, trunc_cf); - vector estimates; - trunc_cf.extrapolate_distinct(search_max_val, search_step_size, estimates); + for (std::size_t i = 7 + (max_terms % 2 == 0); i <= max_terms; i += 2) { + ContinuedFraction truncated_cf(full_cf); + truncate_degree(i, truncated_cf); + std::vector estimates; + truncated_cf.extrapolate_distinct(search_max_val, search_step_size, + estimates); if (check_yield_estimates_stability(estimates)) - return trunc_cf; + return truncated_cf; } } // no stable continued fraction: return null - return ContinuedFraction(); + return {}; } diff --git a/src/continued_fraction.hpp b/src/continued_fraction.hpp index 33c4689..09fbe1b 100644 --- a/src/continued_fraction.hpp +++ b/src/continued_fraction.hpp @@ -1,95 +1,119 @@ -/* Copyright (C) 2013 University of Southern California and - * Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 University of Southern California and + * Andrew D. Smith and Timothy Daley * - * Authors: Andrew D. Smith and Timothy Daley + * Authors: Andrew D. Smith and Timothy Daley * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #ifndef CONTINUED_FRACTION_HPP #define CONTINUED_FRACTION_HPP -#include -#include -#include -#include #include +#include +#include struct ContinuedFraction { // Constructors - ContinuedFraction() : diagonal_idx(0), degree(0ul) {} - ContinuedFraction(const std::vector &ps_cf, - const int di, const size_t dg); + ContinuedFraction() = default; + ContinuedFraction(const std::vector &ps_cf, const int di, + const std::size_t dg); + // Assumes diagonal is 0 + ContinuedFraction(const std::vector &hist, + const std::size_t max_terms); + + // Evaluate the continued fraction + [[nodiscard]] auto + evaluate(const double val) const -> double; // Evaluate the continued fraction - double operator()(const double val) const; + [[nodiscard]] auto + operator()(const double val) const -> double { + return evaluate(val); + } - ////////////////////////////////////////// // Extrapolation functions - // Evaluate the continued fraction estimating distinct - // along a curve from 0 to max_value + // Evaluate the continued fraction estimating distinct along a curve from 0 + // to max_value void extrapolate_distinct(const double max_value, const double step_size, std::vector &estimates) const; - bool is_valid() const {return !cf_coeffs.empty();} - size_t return_degree() const {return degree;} + [[nodiscard]] auto + is_valid() const -> bool { + return !cf_coeffs.empty(); + } + + [[nodiscard]] auto + return_degree() const -> std::size_t { + return degree; + } + + void + extrapolate_curve(const double initial_distinct, const double vals_sum, + const double initial_sample_size, const double step_size, + const double max_sample_size, + std::vector &estimates) const; std::vector ps_coeffs; std::vector cf_coeffs; std::vector offset_coeffs; - int diagonal_idx; - size_t degree; + int diagonal_idx{}; + std::size_t degree{}; }; - // get continued fraction with lower degree -void decrease_degree(const size_t decrement, ContinuedFraction &cf); -void truncate_degree(const size_t truncated_degree, ContinuedFraction &cf); +// get continued fraction with lower degree +void +decrease_degree(const std::size_t decrement, ContinuedFraction &cf); -std::ostream & -operator<<(std::ostream &out, const ContinuedFraction &cf); +void +truncate_degree(const std::size_t truncated_degree, ContinuedFraction &cf); +auto +operator<<(std::ostream &out, const ContinuedFraction &cf) -> std::ostream &; class ContinuedFractionApproximation { public: - ContinuedFractionApproximation(const int di, const size_t mt) : - diagonal_idx(di), max_terms(mt) {} + ContinuedFractionApproximation(const int di, const std::size_t mt) : + diagonal_idx{di}, max_terms{mt} {} // find best cont frac approx for estimating distinct - ContinuedFraction - optimal_cont_frac_distinct(const std::vector &counts_hist) const; + [[nodiscard]] auto + optimal_cf_distinct(const std::vector &counts_hist) const + -> ContinuedFraction; - int get_diagonal() const {return diagonal_idx;} + [[nodiscard]] auto + get_diagonal() const -> int { + return diagonal_idx; + } private: - - int diagonal_idx; // the diagonal to work with for estimates - size_t max_terms; // the maximum number of terms to try for a CF + int diagonal_idx{}; // the diagonal to work with for estimates + std::size_t max_terms{}; // the maximum number of terms to try for a CF /* note: these never change */ - static const size_t min_allowed_degree; + static const std::size_t min_allowed_degree; // largest value to search for lowerbound and stability static const double search_max_val; - //step size for search of lowerbound and stability + // step size for search of lowerbound and stability static const double search_step_size; - }; -bool -check_yield_estimates_stability(const std::vector &estimates); +[[nodiscard]] auto +check_yield_estimates_stability(const std::vector &estimates) -> bool; #endif From cdba61419857772a96d95da74f982786a1867523 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:35:49 -0700 Subject: [PATCH 008/157] src/continued_fraction.hcpp: updating copyright --- src/continued_fraction.cpp | 5 +---- src/continued_fraction.hpp | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/continued_fraction.cpp b/src/continued_fraction.cpp index bcec8a3..c1aed41 100644 --- a/src/continued_fraction.cpp +++ b/src/continued_fraction.cpp @@ -1,7 +1,4 @@ -/* Copyright (C) 2013-2026 University of Southern California and - * Andrew D. Smith and Timothy Daley - * - * Authors: Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 Andrew D. Smith and Timothy Daley * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software diff --git a/src/continued_fraction.hpp b/src/continued_fraction.hpp index 09fbe1b..f9b7f25 100644 --- a/src/continued_fraction.hpp +++ b/src/continued_fraction.hpp @@ -1,7 +1,4 @@ -/* Copyright (C) 2013-2026 University of Southern California and - * Andrew D. Smith and Timothy Daley - * - * Authors: Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 Andrew D. Smith and Timothy Daley * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software From f479d64b746a45b61d8be7f6d0c2d71ae3282d01 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:36:30 -0700 Subject: [PATCH 009/157] src/moment_sequence.cpp: modernization --- src/moment_sequence.cpp | 442 +++++++++++++++++++--------------------- src/moment_sequence.hpp | 72 +++---- 2 files changed, 237 insertions(+), 277 deletions(-) diff --git a/src/moment_sequence.cpp b/src/moment_sequence.cpp index 9f8934e..85cdb10 100644 --- a/src/moment_sequence.cpp +++ b/src/moment_sequence.cpp @@ -1,151 +1,134 @@ -/* Copyright (C) 2013-2015 - * University of Southern California and - * Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 Andrew D. Smith and Timothy Daley * - * Authors: Andrew D. Smith and Timothy Daley + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #include "moment_sequence.hpp" -#include +#include #include -#include -#include -#include #include -#include -#include +#include +#include +#include -using std::string; -using std::vector; -using std::endl; -using std::max; -using std::cerr; -using std::setprecision; -using std::swap; -using std::find_if; -using std::transform; -using std::isfinite; -using std::isinf; - -void -LU_decomp(vector > &A, vector &P) { - - const size_t N = A.size(); - double absA; - size_t i, j, k; - - P.clear(); - for (size_t x = 0; x <= N; x++) - P.push_back(x); - - for (i = 0; i < N; i++) { - double maxA = 0.0; - size_t imax = i; - - for (k = i; k < N; k++) - if ((absA = fabs(A[k][i])) > maxA) { - maxA = absA; - imax = k; - } - - if (imax != i) { - //pivoting P - size_t j = P[i]; - P[i] = P[imax]; - P[imax] = j; - - //pivoting rows of A - vector ptr(A[i]); - A[i] = A[imax]; - A[imax] = ptr; - - //counting pivots starting from N (for determinant) - P[N]++; - } - - for (j = i + 1; j < N; j++) { - A[j][i] /= A[i][i]; - - for (k = i + 1; k < N; k++) - A[j][k] -= A[j][i] * A[i][k]; - } +void +LU_decomp(std::vector> &A, std::vector &P) { + const std::size_t N = std::size(A); + double absA{}; + std::size_t i{}; + std::size_t j{}; + std::size_t k{}; + + P.clear(); + for (std::size_t x = 0; x <= N; x++) + P.push_back(x); + + for (i = 0; i < N; ++i) { + double maxA{}; + std::size_t imax = i; + + for (k = i; k < N; k++) + if ((absA = std::fabs(A[k][i])) > maxA) { + maxA = absA; + imax = k; + } + + if (imax != i) { + // pivoting P + std::size_t j = P[i]; + P[i] = P[imax]; + P[imax] = j; + + // pivoting rows of A + std::swap(A[i], A[imax]); + // std::vector ptr(A[i]); + // A[i] = A[imax]; + // A[imax] = ptr; + + // counting pivots starting from N (for determinant) + ++P[N]; } -} -double -LU_determinant(vector > &A, vector &P) { + for (j = i + 1; j < N; ++j) { + A[j][i] /= A[i][i]; - const size_t N = A.size(); + for (k = i + 1; k < N; k++) + A[j][k] -= A[j][i] * A[i][k]; + } + } +} - double det = A[0][0]; +auto +LU_determinant(std::vector> &A, + std::vector &P) -> double { + const std::size_t N = std::size(A); - for (size_t i = 1; i < N; ++i) - det *= A[i][i]; + double det = A[0][0]; - if ((P[N] - N) % 2 == 0) return det; + for (std::size_t i = 1; i < N; ++i) + det *= A[i][i]; - return -det; + if ((P[N] - N) % 2 == 0) + return det; + + return -det; } ///////////////////////////////////////////////////// // test Hankel moment matrix // ensure moment sequence is positive definite // truncate moment sequence to ensure pos def -size_t -ensure_pos_def_mom_seq(vector &moments, - const double tolerance, - const bool VERBOSE) { - - const size_t min_hankel_dim = 1; - size_t hankel_dim = 2; - if (moments.size() < 2*hankel_dim) { +auto +ensure_pos_def_mom_seq(std::vector &moments, const double tolerance, + const bool VERBOSE) -> std::size_t { + const std::size_t min_hankel_dim = 1; + std::size_t hankel_dim = 2; + if (std::size(moments) < 2 * hankel_dim) { if (VERBOSE) - cerr << "too few moments" << endl; + std::cerr << "too few moments" << std::endl; return min_hankel_dim; } bool ACCEPT_HANKEL = true; - while (ACCEPT_HANKEL && (2*hankel_dim - 1 < moments.size())) { - - vector > hankel_mat(hankel_dim, vector(hankel_dim, 0.0)); - for (size_t c_idx = 0; c_idx < hankel_dim; c_idx++) - for (size_t r_idx = 0; r_idx < hankel_dim; r_idx++) - hankel_mat[c_idx][r_idx] = moments[c_idx + r_idx]; - - vector perm; + while (ACCEPT_HANKEL && (2 * hankel_dim - 1 < std::size(moments))) { + std::vector> hankel_mat( + hankel_dim, std::vector(hankel_dim, 0.0)); + for (std::size_t c_idx = 0; c_idx < hankel_dim; c_idx++) + for (std::size_t r_idx = 0; r_idx < hankel_dim; r_idx++) + hankel_mat[c_idx][r_idx] = moments[c_idx + r_idx]; + + std::vector perm; LU_decomp(hankel_mat, perm); const double hankel_mat_det = LU_determinant(hankel_mat, perm); - vector > shift_hankel_matrix(hankel_dim, vector(hankel_dim, 0.0)); - for (size_t c_idx = 0; c_idx < hankel_dim; c_idx++) - for (size_t r_idx = 0; r_idx < hankel_dim; r_idx++) - shift_hankel_matrix[c_idx][r_idx] = moments[c_idx + r_idx + 1]; + std::vector> shift_hankel_matrix( + hankel_dim, std::vector(hankel_dim, 0.0)); + for (std::size_t c_idx = 0; c_idx < hankel_dim; c_idx++) + for (std::size_t r_idx = 0; r_idx < hankel_dim; r_idx++) + shift_hankel_matrix[c_idx][r_idx] = moments[c_idx + r_idx + 1]; - vector s_perm; + std::vector s_perm; LU_decomp(shift_hankel_matrix, s_perm); - const double shift_hankel_mat_det = LU_determinant(shift_hankel_matrix, s_perm); + const double shift_hankel_mat_det = + LU_determinant(shift_hankel_matrix, s_perm); if (VERBOSE) { - cerr << "dim" << '\t' - << "hankel_det" << '\t' - << "shifted_hankel_det" << endl; - cerr << hankel_dim << '\t' - << hankel_mat_det << '\t' - << shift_hankel_mat_det << endl; + std::cerr << "dim" << '\t' << "hankel_det" << '\t' << "shifted_hankel_det" + << std::endl; + std::cerr << hankel_dim << '\t' << hankel_mat_det << '\t' + << shift_hankel_mat_det << std::endl; } if (hankel_mat_det > tolerance && shift_hankel_mat_det > tolerance) { @@ -155,32 +138,29 @@ ensure_pos_def_mom_seq(vector &moments, else { ACCEPT_HANKEL = false; hankel_dim--; - moments.resize(2*hankel_dim); + moments.resize(2 * hankel_dim); return hankel_dim; } } - return max(hankel_dim - 1, min_hankel_dim); + return std::max(hankel_dim - 1, min_hankel_dim); } -///////////////////////////////////////////////////// -// 3 term relations +/// 3 term relations -// check 3 term recurrence to avoid non-positive elements -// truncate if non-positive element found +// check 3 term recurrence to avoid non-positive elements truncate if +// non-positive element found static void -check_three_term_relation(vector &a, vector &b) { - +check_three_term_relation(std::vector &a, std::vector &b) { // abort if first entry is zero or smaller if (a[0] <= 0.0) { a.clear(); b.clear(); } - - for (size_t i = 0; i < b.size(); i++) + for (std::size_t i = 0; i < std::size(b); ++i) // ADS: some strange logic here - if (b[i] <= 0.0 || !isfinite(b[i]) || - a[i + 1] <= 0.0 || !isfinite(a[i + 1])) { + if (b[i] <= 0.0 || !std::isfinite(b[i]) || a[i + 1] <= 0.0 || + !std::isfinite(a[i + 1])) { b.resize(i); a.resize(i + 1); break; @@ -190,43 +170,41 @@ check_three_term_relation(vector &a, vector &b) { // check the moment sequence to avoid non-positive elements and // truncate at first non-positive element if found static void -check_moment_sequence(vector &obs_moms) { - - if (obs_moms[0] <= 0.0 || !isfinite(obs_moms[0])) +check_moment_sequence(std::vector &obs_moms) { + if (obs_moms[0] <= 0.0 || !std::isfinite(obs_moms[0])) obs_moms.clear(); - for (size_t i = 1; i < obs_moms.size(); i++) { - if (obs_moms[i] <= 0.0 || !isfinite(obs_moms[i])) { + for (std::size_t i = 1; i < std::size(obs_moms); ++i) + if (obs_moms[i] <= 0.0 || !std::isfinite(obs_moms[i])) { obs_moms.resize(i + 1); break; } - } } - void -MomentSequence::unmodified_Chebyshev(const bool VERBOSE) { +MomentSequence::unmodified_Chebyshev() { + const auto n_points = static_cast(floor(std::size(moments) / 2)); + std::vector a(n_points, 0.0); + std::vector b(n_points - 1, 0.0); - const size_t n_points = static_cast(floor(moments.size()/2)); - vector a(n_points, 0.0); - vector b(n_points - 1, 0.0); - - vector< vector > sigma(2*n_points, vector(2*n_points, 0.0)); + std::vector> sigma( + 2 * n_points, std::vector(2 * n_points, 0.0)); // initialization - a[0] = moments[1]/moments[0]; + a[0] = moments[1] / moments[0]; // sigma[-1][l] = 0 - for (size_t l = 0; l < 2*n_points; l++) + for (std::size_t l = 0; l < 2 * n_points; l++) sigma[0][l] = moments[l]; - for (size_t k = 1; k <= n_points; k++) { - for (size_t l = k; l < 2*n_points - k; l++) { - sigma[k][l] = sigma[k-1][l+1] - a[k-1]*sigma[k-1][l]; + for (std::size_t k = 1; k <= n_points; k++) { + for (std::size_t l = k; l < 2 * n_points - k; l++) { + sigma[k][l] = sigma[k - 1][l + 1] - a[k - 1] * sigma[k - 1][l]; if (k > 1) - sigma[k][l] -= b[k-2]*sigma[k-2][l]; + sigma[k][l] -= b[k - 2] * sigma[k - 2][l]; } if (k != n_points) { - a[k] = sigma[k][k+1]/sigma[k][k] - sigma[k-1][k]/sigma[k-1][k-1]; - b[k-1] = sigma[k][k]/sigma[k-1][k-1]; + a[k] = + sigma[k][k + 1] / sigma[k][k] - sigma[k - 1][k] / sigma[k - 1][k - 1]; + b[k - 1] = sigma[k][k] / sigma[k - 1][k - 1]; } } @@ -236,30 +214,30 @@ MomentSequence::unmodified_Chebyshev(const bool VERBOSE) { // un-normalized 3 term recurrence void -MomentSequence::full_3term_recurrence(const bool VERBOSE, - vector &full_alpha, - vector &full_beta) { - - const size_t n_points = static_cast(floor(moments.size()/2)); - vector a(n_points, 0.0); - vector b(n_points - 1, 0.0); - - vector< vector > sigma(2*n_points, vector(2*n_points, 0.0)); +MomentSequence::full_3term_recurrence(std::vector &full_alpha, + std::vector &full_beta) { + const auto n_points = static_cast(floor(std::size(moments) / 2)); + std::vector a(n_points, 0.0); + std::vector b(n_points - 1, 0.0); + + std::vector> sigma( + 2 * n_points, std::vector(2 * n_points, 0.0)); // initialization - a[0] = moments[1]/moments[0]; + a[0] = moments[1] / moments[0]; // sigma[-1][l] = 0 - for (size_t l = 0; l < 2*n_points; l++) + for (std::size_t l = 0; l < 2 * n_points; l++) sigma[0][l] = moments[l]; - for (size_t k = 1; k <= n_points; k++) { - for (size_t l = k; l < 2*n_points - k; l++) { - sigma[k][l] = sigma[k-1][l+1] - a[k-1]*sigma[k-1][l]; + for (std::size_t k = 1; k <= n_points; k++) { + for (std::size_t l = k; l < 2 * n_points - k; l++) { + sigma[k][l] = sigma[k - 1][l + 1] - a[k - 1] * sigma[k - 1][l]; if (k > 1) - sigma[k][l] -= b[k-2]*sigma[k-2][l]; + sigma[k][l] -= b[k - 2] * sigma[k - 2][l]; } if (k != n_points) { - a[k] = sigma[k][k+1]/sigma[k][k] - sigma[k-1][k]/sigma[k-1][k-1]; - b[k-1] = sigma[k][k]/sigma[k-1][k-1]; + a[k] = + sigma[k][k + 1] / sigma[k][k] - sigma[k - 1][k] / sigma[k - 1][k - 1]; + b[k - 1] = sigma[k][k] / sigma[k - 1][k - 1]; } } @@ -267,22 +245,20 @@ MomentSequence::full_3term_recurrence(const bool VERBOSE, full_beta.swap(b); } - //////////////////////////////////////////////////// // Constructor -MomentSequence::MomentSequence(const vector &obs_moms) : +MomentSequence::MomentSequence(const std::vector &obs_moms) : moments(obs_moms) { - vector holding_moms(moments); + std::vector holding_moms(moments); // make sure the moments are all positive check_moment_sequence(holding_moms); moments = holding_moms; // calculate 3-term recurrence - unmodified_Chebyshev(false); + unmodified_Chebyshev(); } - ///////////////////////////////////////////////////// // Quadrature Methods @@ -291,64 +267,62 @@ MomentSequence::MomentSequence(const vector &obs_moms) : // one iteration is Z_N-1*Z_N-2*...*Z_1*X*Z_1*...*Z_N-1 // Z_j is givens matrix to zero out the j+1,j'th element of X static void -QRiteration(vector &alpha, vector &beta, - vector &weights) { - +QRiteration(std::vector &alpha, std::vector &beta, + std::vector &weights) { // initialize variables - vector sin_theta(alpha.size(), 0.0); - vector cos_theta(alpha.size(), 0.0); + std::vector sin_theta(std::size(alpha), 0.0); + std::vector cos_theta(std::size(alpha), 0.0); - vector a(alpha.size(), 0.0); - vector a_bar(alpha.size(), 0.0); + std::vector a(std::size(alpha), 0.0); + std::vector a_bar(std::size(alpha), 0.0); a_bar[0] = alpha[0]; - vector b(beta); - vector b_bar(alpha.size(), 0.0); + std::vector b(beta); + std::vector b_bar(std::size(alpha), 0.0); b_bar[0] = alpha[0]; - vector b_tilde(alpha.size(), 0.0); + std::vector b_tilde(std::size(alpha), 0.0); b_tilde[0] = beta[0]; - vector d(alpha.size(), 0.0); + std::vector d(std::size(alpha), 0.0); d[0] = beta[0]; - vector z(weights); - vector z_bar(weights.size(), 0.0); + std::vector z(weights); + std::vector z_bar(std::size(weights), 0.0); z_bar[0] = z[0]; - for (size_t j = 0; j < alpha.size() - 1; j++) { - + for (std::size_t j = 0; j + 1 < std::size(alpha); ++j) { // for d and b_bar, j here is j-1 in G&W if (d[j] == 0.0 && b_bar[j] == 0.0) { sin_theta[j] = 0.0; cos_theta[j] = 1.0; } else { - sin_theta[j] = d[j]/sqrt(d[j]*d[j] + b_bar[j]*b_bar[j]); - cos_theta[j] = b_bar[j]/sqrt(d[j]*d[j] + b_bar[j]*b_bar[j]); + sin_theta[j] = d[j] / sqrt(d[j] * d[j] + b_bar[j] * b_bar[j]); + cos_theta[j] = b_bar[j] / sqrt(d[j] * d[j] + b_bar[j] * b_bar[j]); } - a[j] = (a_bar[j]*cos_theta[j]*cos_theta[j] + - 2*b_tilde[j]*cos_theta[j]*sin_theta[j] + - alpha[j+1]*sin_theta[j]*sin_theta[j]); + a[j] = (a_bar[j] * cos_theta[j] * cos_theta[j] + + 2 * b_tilde[j] * cos_theta[j] * sin_theta[j] + + alpha[j + 1] * sin_theta[j] * sin_theta[j]); - a_bar[j+1] = (a_bar[j]*sin_theta[j]*sin_theta[j] - - 2*b_tilde[j]*cos_theta[j]*sin_theta[j] + - alpha[j+1]*cos_theta[j]*cos_theta[j]); + a_bar[j + 1] = (a_bar[j] * sin_theta[j] * sin_theta[j] - + 2 * b_tilde[j] * cos_theta[j] * sin_theta[j] + + alpha[j + 1] * cos_theta[j] * cos_theta[j]); if (j != 0) - b[j-1] = sqrt(d[j]*d[j] + b_bar[j]*b_bar[j]); + b[j - 1] = sqrt(d[j] * d[j] + b_bar[j] * b_bar[j]); - b_bar[j+1] = - ((a_bar[j] - alpha[j+1])*sin_theta[j]*cos_theta[j] + - b_tilde[j]*(sin_theta[j]*sin_theta[j] - cos_theta[j]*cos_theta[j])); + b_bar[j + 1] = ((a_bar[j] - alpha[j + 1]) * sin_theta[j] * cos_theta[j] + + b_tilde[j] * (sin_theta[j] * sin_theta[j] - + cos_theta[j] * cos_theta[j])); - b_tilde[j+1] = -beta[j+1]*cos_theta[j]; + b_tilde[j + 1] = -beta[j + 1] * cos_theta[j]; - d[j+1] = beta[j+1]*sin_theta[j]; + d[j + 1] = beta[j + 1] * sin_theta[j]; - z[j] = z_bar[j]*cos_theta[j] + weights[j+1]*sin_theta[j]; + z[j] = z_bar[j] * cos_theta[j] + weights[j + 1] * sin_theta[j]; - z_bar[j+1] = z_bar[j]*sin_theta[j] - weights[j+1]*cos_theta[j]; + z_bar[j + 1] = z_bar[j] * sin_theta[j] - weights[j + 1] * cos_theta[j]; } // last entries set equal to final "holding" values @@ -356,71 +330,69 @@ QRiteration(vector &alpha, vector &beta, b.back() = b_bar.back(); z.back() = z_bar.back(); - swap(alpha, a); - swap(beta, b); - swap(weights, z); + std::swap(alpha, a); + std::swap(beta, b); + std::swap(weights, z); } - -static bool -check_positivity(const vector &v) { - return find_if(begin(v), end(v), - [](const double x) {return x <= 0.0 || isinf(x);}) == end(v); +static auto +check_positivity(const std::vector &v) -> bool { + const auto not_positive = [](const auto x) { + return x <= 0.0 || std::isinf(x); + }; + return std::ranges::find_if(v, not_positive) == std::cend(v); } - -bool -MomentSequence::Lower_quadrature_rules(const bool VERBOSE, - const size_t n_points, +auto +MomentSequence::Lower_quadrature_rules(const std::size_t n_points, const double tol, - const size_t max_iter, - vector &points, - vector &weights) { - - // make sure that points.size() will be less than n_points - vector a(alpha); - a.resize((n_points < alpha.size()) ? n_points : alpha.size()); - vector b(beta); - b.resize((n_points - 1 < beta.size()) ? n_points - 1 : beta.size()); + const std::size_t max_iter, + std::vector &points, + std::vector &weights) -> bool { + // make sure that std::size(points) will be less than n_points + std::vector a(alpha); + a.resize((n_points < std::size(alpha)) ? n_points : std::size(alpha)); + std::vector b(beta); + b.resize((n_points - 1 < std::size(beta)) ? n_points - 1 : std::size(beta)); check_three_term_relation(a, b); // See Gautschi pgs 10-13, // the nu here is the square of the off-diagonal // of the Jacobi matrix - for (size_t i = 0; i < b.size(); i++) - b[i] = sqrt(b[i]); + for (double &b_val : b) + b_val = std::sqrt(b_val); - vector eigenvec(a.size(), 0.0); + std::vector eigenvec(std::size(a), 0.0); eigenvec[0] = 1.0; - vector eigenvals(a); - vector qr_beta(b); + std::vector eigenvals(a); + std::vector qr_beta(b); // in QR, off-diagonals go to zero use off diags for convergence - double error_sum = 0.0; - for (size_t i = 0; i < qr_beta.size(); i++) - error_sum += fabs(qr_beta[i]); + double error_sum{}; + for (const double val : qr_beta) + error_sum += std::fabs(val); - size_t iter = 0; + std::size_t iter{}; while (iter < max_iter && error_sum > tol) { QRiteration(eigenvals, qr_beta, eigenvec); error_sum = 0.0; - for (size_t i = 0; i < qr_beta.size(); i++) - error_sum += fabs(qr_beta[i]); + for (const double val : qr_beta) + error_sum += std::fabs(val); iter++; } // eigenvalues are on diagonal of J const bool points_are_positive = check_positivity(eigenvals); if (points_are_positive) { - swap(points, eigenvals); - swap(weights, eigenvec); + std::swap(points, eigenvals); + std::swap(weights, eigenvec); } // square entries in the weights vector - transform(begin(weights), end(weights), begin(weights), - [](const double x) {return x*x;}); + const auto sqr = [](const auto x) { return x * x; }; + std::ranges::transform(weights, std::begin(weights), sqr); return points_are_positive; } diff --git a/src/moment_sequence.hpp b/src/moment_sequence.hpp index 0f7ccbb..398ccdd 100644 --- a/src/moment_sequence.hpp +++ b/src/moment_sequence.hpp @@ -1,63 +1,52 @@ -/* Copyright (C) 2013 University of Southern California and - * Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 Andrew D. Smith and Timothy Daley * - * Authors: Andrew D. Smith and Timothy Daley + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #ifndef MOMENT_SEQUENCE_HPP #define MOMENT_SEQUENCE_HPP -#include -#include #include +#include -// test Hankel moment matrix to ensure the moment sequence -// is positive definite -size_t ensure_pos_def_mom_seq(std::vector &moments, - const double tolerance, - const bool VERBOSE); +// test Hankel moment matrix to ensure the moment sequence is positive definite +auto +ensure_pos_def_mom_seq(std::vector &moments, const double tolerance, + const bool VERBOSE) -> size_t; struct MomentSequence { - // Constructors - MomentSequence() {} - MomentSequence(const std::vector &obs_moms); - - MomentSequence(const std::vector &a, - const std::vector &b): - alpha(a), beta(b) {}; - + MomentSequence() = default; + explicit MomentSequence(const std::vector &obs_moms); + MomentSequence(const std::vector &alpha, + const std::vector &beta) : alpha{alpha}, beta{beta} {}; // Estimate 3-term recurrence // these will be removed from the header when they are tested - void unmodified_Chebyshev(const bool VERBOSE); + void + unmodified_Chebyshev(); - void full_3term_recurrence(const bool VERBOSE, - std::vector &full_alpha, - std::vector &full_beta); - - // quadrature rules using QR on Jacobi matrix - bool Lower_quadrature_rules(const bool VERBOSE, - const size_t n_points, - const double tolerance, - const size_t max_iter, - std::vector &points, - std::vector &weights); + void + full_3term_recurrence(std::vector &full_alpha, + std::vector &full_beta); + // quadrature rules using QR on Jacobi matrix + auto + Lower_quadrature_rules(const size_t n_points, const double tolerance, + const size_t max_iter, std::vector &points, + std::vector &weights) -> bool; std::vector moments; // 3-term recurrence @@ -65,5 +54,4 @@ struct MomentSequence { std::vector beta; }; - #endif From 8b448eee6af3dbd93a20c6dc938939d831a846a1 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:56:42 -0700 Subject: [PATCH 010/157] src/continued_fraction.hcpp: fixing output formatting for ContinuedFraction objects --- src/continued_fraction.cpp | 25 ++++++++++++------------- src/continued_fraction.hpp | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/continued_fraction.cpp b/src/continued_fraction.cpp index c1aed41..2d0620b 100644 --- a/src/continued_fraction.cpp +++ b/src/continued_fraction.cpp @@ -23,9 +23,8 @@ #include #include -// ADS: the std::pow function is used frequently to get (-1)^x for -// integer x. This doesn't make sense, and should be replaced at some -// point. +// ADS: the std::pow function is used frequently to get (-1)^x for integer +// x. This doesn't make sense, and should be replaced at some point. /* QUOTIENT DIFFERENCE ALGORITHM: compute continued fraction * coefficients vector for power series coefficients & vector for @@ -281,23 +280,23 @@ ContinuedFraction::extrapolate_curve(const double initial_distinct, } } -auto -operator<<(std::ostream &the_stream, - const ContinuedFraction &cf) -> std::ostream & { +[[nodiscard]] auto +ContinuedFraction::tostring() const -> std::string { + std::ostringstream the_stream; std::ios_base::fmtflags orig_flags = the_stream.flags(); the_stream.setf(std::ios_base::fixed, std::ios_base::floatfield); the_stream.precision(2); the_stream << "OFFSET_COEFFS" << '\t' << "PS_COEFFS" << '\n'; - const std::size_t offset = std::size(cf.offset_coeffs); + const std::size_t offset = std::size(offset_coeffs); for (std::size_t i = 0; i < offset; ++i) - the_stream << std::setw(12) << cf.offset_coeffs[i] << '\t' << std::setw(12) - << cf.ps_coeffs[i] << '\n'; + the_stream << std::setw(12) << offset_coeffs[i] << '\t' << std::setw(12) + << ps_coeffs[i] << '\n'; the_stream << "CF_COEFFS" << '\n'; - for (std::size_t i = 0; i < std::size(cf.cf_coeffs); ++i) - the_stream << std::setw(12) << cf.cf_coeffs[i] << '\t' << std::setw(12) - << cf.ps_coeffs[i + offset] << '\n'; + for (std::size_t i = 0; i < std::size(cf_coeffs); ++i) + the_stream << std::setw(12) << cf_coeffs[i] << '\t' << std::setw(12) + << ps_coeffs[i + offset] << '\n'; the_stream.flags(orig_flags); - return the_stream; + return the_stream.str(); } // estimate yields by evaluating the CF at given points diff --git a/src/continued_fraction.hpp b/src/continued_fraction.hpp index f9b7f25..8753657 100644 --- a/src/continued_fraction.hpp +++ b/src/continued_fraction.hpp @@ -64,6 +64,9 @@ struct ContinuedFraction { const double max_sample_size, std::vector &estimates) const; + [[nodiscard]] auto + tostring() const -> std::string; + std::vector ps_coeffs; std::vector cf_coeffs; std::vector offset_coeffs; @@ -78,8 +81,18 @@ decrease_degree(const std::size_t decrement, ContinuedFraction &cf); void truncate_degree(const std::size_t truncated_degree, ContinuedFraction &cf); -auto -operator<<(std::ostream &out, const ContinuedFraction &cf) -> std::ostream &; +inline auto +operator<<(std::ostream &out, const ContinuedFraction &cf) -> std::ostream & { + return out << cf.tostring(); +} + +template <> +struct std::formatter : std::formatter { + auto + format(const ContinuedFraction &cf, auto &ctx) const { + return std::formatter::format(cf.tostring(), ctx); + } +}; class ContinuedFractionApproximation { public: From 2803d86644610ba51e715f9b095ea4175ff8eec1 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 16:57:01 -0700 Subject: [PATCH 011/157] src/load_data_for_complexity.hcpp: modernization --- src/load_data_for_complexity.cpp | 825 ++++++++++++++++--------------- src/load_data_for_complexity.hpp | 92 ++-- 2 files changed, 460 insertions(+), 457 deletions(-) diff --git a/src/load_data_for_complexity.cpp b/src/load_data_for_complexity.cpp index 7012cbf..906769e 100644 --- a/src/load_data_for_complexity.cpp +++ b/src/load_data_for_complexity.cpp @@ -1,53 +1,46 @@ -/* Copyright (C) 2014 University of Southern California and - * Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 Andrew D. Smith and Timothy Daley * - * Authors: Andrew D. Smith and Timothy Daley + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #include "load_data_for_complexity.hpp" -#include -#include -#include -#include -#include +#include "bamxx/bamxx.hpp" + +#include #include "GenomicRegion.hpp" #include "MappedRead.hpp" -using std::string; -using std::vector; -using std::priority_queue; -using std::min; -using std::endl; -using std::max; -using std::cerr; -using std::unordered_map; -using std::runtime_error; -using std::mt19937; - -////////////////////////////////////////////////////////////////////// -// Data imputation -///////////////////////////////////////////////////////////////////// +#include -static bool +#include // std::min +#include +#include +#include +#include +#include +#include +#include // std::swap + +/// Data imputation + +static auto update_pe_duplicate_counts_hist(const GenomicRegion &curr_gr, const GenomicRegion &prev_gr, - vector &counts_hist, - size_t ¤t_count){ + std::vector &counts_hist, + std::size_t ¤t_count) -> bool { // check if reads are sorted if (curr_gr.same_chrom(prev_gr) && curr_gr.get_start() < prev_gr.get_start() && @@ -60,173 +53,237 @@ update_pe_duplicate_counts_hist(const GenomicRegion &curr_gr, if (!curr_gr.same_chrom(prev_gr) || curr_gr.get_start() != prev_gr.get_start() || curr_gr.get_end() != prev_gr.get_end()) { - // histogram is too small, resize - if (counts_hist.size() < current_count + 1) + if (std::size(counts_hist) < current_count + 1) counts_hist.resize(current_count + 1, 0.0); ++counts_hist[current_count]; current_count = 1; } - else // next read is same, update current_count + else // next read is same, update current_count ++current_count; return true; } - static void update_se_duplicate_counts_hist(const GenomicRegion &curr_gr, const GenomicRegion &prev_gr, - const string input_file_name, - vector &counts_hist, - size_t ¤t_count){ + const std::string input_file_name, + std::vector &counts_hist, + std::size_t ¤t_count) { // check if reads are sorted - if (curr_gr.same_chrom(prev_gr) && - curr_gr.get_start() < prev_gr.get_start()) - throw runtime_error("locations unsorted in: " - + input_file_name); + if (curr_gr.same_chrom(prev_gr) && curr_gr.get_start() < prev_gr.get_start()) + throw std::runtime_error("locations unsorted in: " + input_file_name); if (!curr_gr.same_chrom(prev_gr) || curr_gr.get_start() != prev_gr.get_start()) - // next read is new, update counts_hist to include current_count - { - // histogram is too small, resize - if(counts_hist.size() < current_count + 1) - counts_hist.resize(current_count + 1, 0.0); - ++counts_hist[current_count]; - current_count = 1; - } - else // next read is same, update current_count + // next read is new, update counts_hist to include current_count + { + // histogram is too small, resize + if (std::size(counts_hist) < current_count + 1) + counts_hist.resize(current_count + 1, 0.0); + ++counts_hist[current_count]; + current_count = 1; + } + else // next read is same, update current_count ++current_count; } - - -/////comparison function for priority queue///////////////// +// comparison function for priority queue /**************** FOR CLARITY BELOW WHEN COMPARING READS *************/ -static inline bool -chrom_greater(const GenomicRegion &a, const GenomicRegion &b) { +static inline auto +chrom_greater(const GenomicRegion &a, const GenomicRegion &b) -> bool { return a.get_chrom() > b.get_chrom(); } -static inline bool -same_start(const GenomicRegion &a, const GenomicRegion &b) { +static inline auto +same_start(const GenomicRegion &a, const GenomicRegion &b) -> bool { return a.get_start() == b.get_start(); } -static inline bool -start_greater(const GenomicRegion &a, const GenomicRegion &b) { +static inline auto +start_greater(const GenomicRegion &a, const GenomicRegion &b) -> bool { return a.get_start() > b.get_start(); } -static inline bool -end_greater(const GenomicRegion &a, const GenomicRegion &b) { +static inline auto +end_greater(const GenomicRegion &a, const GenomicRegion &b) -> bool { return a.get_end() > b.get_end(); } /******************************************************************************/ - struct GenomicRegionOrderChecker { - bool operator()(const GenomicRegion &prev, const GenomicRegion &gr) const { + auto + operator()(const GenomicRegion &prev, const GenomicRegion &gr) const -> bool { return start_check(prev, gr); } - static bool - start_check(const GenomicRegion &prev, const GenomicRegion &gr) { - return (chrom_greater(prev, gr) - || (prev.same_chrom(gr) && start_greater(prev, gr)) - || (prev.same_chrom(gr) && same_start(prev, gr) - && end_greater(prev, gr))); + static auto + start_check(const GenomicRegion &prev, const GenomicRegion &gr) -> bool { + return ( + chrom_greater(prev, gr) || + (prev.same_chrom(gr) && start_greater(prev, gr)) || + (prev.same_chrom(gr) && same_start(prev, gr) && end_greater(prev, gr))); } }; +using ReadPQ = std::priority_queue, + GenomicRegionOrderChecker>; -typedef priority_queue, - GenomicRegionOrderChecker> ReadPQ; - - -static bool -is_ready_to_pop(const ReadPQ &pq, - const GenomicRegion &gr, - const size_t max_width) { +static auto +is_ready_to_pop(const ReadPQ &pq, const GenomicRegion &gr, + const std::size_t max_width) -> bool { return !pq.top().same_chrom(gr) || - pq.top().get_end() + max_width < gr.get_start(); + pq.top().get_end() + max_width < gr.get_start(); } - static void empty_pq(GenomicRegion &curr_gr, GenomicRegion &prev_gr, - size_t ¤t_count, vector &counts_hist, - ReadPQ &read_pq, const string &input_file_name) { - + std::size_t ¤t_count, std::vector &counts_hist, + ReadPQ &read_pq, const std::string &input_file_name) { curr_gr = read_pq.top(); read_pq.pop(); // update counts hist - const bool UPDATE_SUCCESS = - update_pe_duplicate_counts_hist(curr_gr, prev_gr, counts_hist, - current_count); + const bool UPDATE_SUCCESS = update_pe_duplicate_counts_hist( + curr_gr, prev_gr, counts_hist, current_count); if (!UPDATE_SUCCESS) { std::ostringstream oss; oss << "reads unsorted in: " << input_file_name << "\n" << "prev = \t" << prev_gr << "\n" << "curr = \t" << curr_gr << "\n" << "Increase seg_len if in paired end mode"; - throw runtime_error(oss.str()); + throw std::runtime_error(oss.str()); } prev_gr = curr_gr; } - /* * This code is used to deal with read data in BAM format. */ #ifdef HAVE_HTSLIB // switching dependency on bamtools to samtools -#include "htslib_wrapper_deprecated.hpp" - -size_t -load_counts_BAM_se(const string &input_file_name, - vector &counts_hist) { - const string mapper = "general"; - SAMReader_deprecated sam_reader(input_file_name, mapper); - if(!sam_reader) - throw runtime_error("problem opening input file " - + input_file_name); - - SAMRecord samr; - sam_reader >> samr; - size_t n_reads = 1; +// #include "htslib_wrapper_deprecated.hpp" + +std::size_t +load_counts_BAM_se(const std::string &input_file_name, + std::vector &counts_hist) { + // const std::string mapper = "general"; + // SAMReader_deprecated sam_reader(input_file_name, mapper); + // if (!sam_reader) + // throw std::runtime_error("problem opening input file " + + // input_file_name); + + // open the hts SAM/BAM input file and get the header + bamxx::bam_in hts(input_file_name); + if (!hts) + throw std::runtime_error("failed to open input file"); + bamxx::bam_header hdr(hts); + if (!hdr) + throw std::runtime_error("failed to read header"); + + bamxx::bam_rec aln; + + // SAMRecord samr; + // sam_reader >> samr; + std::size_t n_reads = 1; // resize vals_hist, make sure it starts out empty counts_hist.clear(); counts_hist.resize(2, 0.0); - size_t current_count = 1; + std::size_t current_count = 1; + + const auto get_seq = [](const auto &aln) { + auto qlen = aln.b->core.l_qseq; + auto seq = bam_get_seq(aln.b); + std::string seq_str; + for (auto i = 0; i < qlen; ++i) + seq_str += seq_nt16_str[bam_seqi(seq, i)]; + return seq_str; + }; + + const auto get_mr = [&](const auto &aln) { + MappedRead mr; + // clang-format off + mr.r = GenomicRegion( + sam_hdr_tid2name(hdr.h, aln.b->core.tid), + aln.b->core.pos, + bam_endpos(aln.b), + bam_get_qname(aln.b), + 0.0, + bam_is_rev(aln.b) ? '-' : '+' + ); + // clang-format on + mr.seq = get_seq(aln); + mr.scr.resize(aln.b->core.l_qseq, 'B'); + return mr; + }; + // return MappedRead{ + // GenomicRegion(sam_hdr_tid2name(hdr.h, aln.b->core.tid), + // aln.b->core.pos, bam_endpos(aln.b), + // bam_get_qname(aln.b), 0.0, + // bam_is_rev(aln.b) ? '-' : '+'), + // get_seq(aln), + // std::string(aln.b->core.l_qseq, 'B') + // }; + // // clang-format on + // }; + + // /*! @abstract the read is paired in sequencing, no matter whether it is + // mapped in a pair */ + // #define BAM_FPAIRED 1 + // /*! @abstract the read is mapped in a proper pair */ + // #define BAM_FPROPER_PAIR 2 + // /*! @abstract the read itself is unmapped; conflictive with + // BAM_FPROPER_PAIR */ #define BAM_FUNMAP 4 + // /*! @abstract the mate is unmapped */ + // #define BAM_FMUNMAP 8 + // /*! @abstract the read is mapped to the reverse strand */ + // #define BAM_FREVERSE 16 + // /*! @abstract the mate is mapped to the reverse strand */ + // #define BAM_FMREVERSE 32 + // /*! @abstract this is read1 */ + // #define BAM_FREAD1 64 + // /*! @abstract this is read2 */ + // #define BAM_FREAD2 128 + // /*! @abstract not primary alignment */ + // #define BAM_FSECONDARY 256 + // /*! @abstract QC failure */ + // #define BAM_FQCFAIL 512 + // /*! @abstract optical or PCR duplicate */ + // #define BAM_FDUP 1024 + // /*! @abstract supplementary alignment */ + // #define BAM_FSUPPLEMENTARY 2048 + + constexpr auto check_flag = [&](const auto &aln, const auto flag) { + return (aln.b->core.flag & flag) != 0; + }; + constexpr auto get_tid = [](const auto &aln) { return aln.b->core.tid; }; MappedRead prev_mr, curr_mr; - prev_mr = samr.mr; + prev_mr = get_mr(aln); // samr.mr; - while (sam_reader >> samr) { + while (hts.read(hdr, aln)) { + const std::int32_t tid = get_tid(aln); + if (tid == -1) // ADS: skip unmapped lines reads + continue; + // while (sam_reader >> samr) { // only convert mapped and primary reads - if (samr.is_primary && samr.is_mapped) { - - // ignore unmapped reads & secondary alignments - if (!(samr.is_mapping_paired) || - (samr.is_mapping_paired && samr.is_Trich)){ - //only count unpaired reads or the left mate of paired reads - - curr_mr = samr.mr; - update_se_duplicate_counts_hist(curr_mr.r, prev_mr.r, - input_file_name, - counts_hist, - current_count); - - // update number of reads and prev read - ++n_reads; - prev_mr = samr.mr; - } + if (!check_flag(aln, BAM_FUNMAP) && !check_flag(aln, BAM_FSECONDARY) && + (!check_flag(aln, BAM_FPROPER_PAIR) || check_flag(aln, BAM_FREAD1))) { + // if (samr.is_primary && samr.is_mapped) { + // // ignore unmapped reads & secondary alignments + // if (!samr.is_mapping_paired || samr.is_mapping_paired && + // samr.is_Trich) { + // only count unpaired reads or the left mate of paired reads + curr_mr = get_mr(aln); // samr.mr; + update_se_duplicate_counts_hist(curr_mr.r, prev_mr.r, input_file_name, + counts_hist, current_count); + // update number of reads and prev read + ++n_reads; + prev_mr = curr_mr; // samr.mr; + // } } } // to account for the last read compared to the one before it. - if (counts_hist.size() < current_count + 1) + if (std::size(counts_hist) < current_count + 1) counts_hist.resize(current_count + 1, 0.0); ++counts_hist[current_count]; @@ -236,155 +293,138 @@ load_counts_BAM_se(const string &input_file_name, /********Below are functions for merging pair-end reads********/ static bool -merge_mates(const size_t suffix_len, const size_t range, - const GenomicRegion &one, const GenomicRegion &two, - GenomicRegion &merged, int &len) { - +merge_mates(const std::size_t suffix_len, const GenomicRegion &one, + const GenomicRegion &two, GenomicRegion &merged, int &len) { assert(one.same_chrom(two)); - const size_t read_start = min(one.get_start(), two.get_start()); - const size_t read_end = max(one.get_end(), two.get_end()); + const std::size_t read_start = std::min(one.get_start(), two.get_start()); + const std::size_t read_end = std::max(one.get_end(), two.get_end()); len = read_end - read_start; - if (len < 0) { - // cerr << one << endl; - // cerr << two << endl; + if (len < 0) return false; - } merged = one; merged.set_start(read_start); merged.set_end(read_end); merged.set_score(one.get_score() + two.get_score()); - const string name(one.get_name()); - merged.set_name("FRAG:" + name.substr(0, name.size() - suffix_len)); + const std::string name(one.get_name()); + merged.set_name("FRAG:" + name.substr(0, std::size(name) - suffix_len)); return true; } inline static bool -same_read(const size_t suffix_len, - const MappedRead &a, const MappedRead &b) { - const string sa(a.r.get_name()); - const string sb(b.r.get_name()); +same_read(const std::size_t suffix_len, const MappedRead &a, + const MappedRead &b) { + const std::string sa(a.r.get_name()); + const std::string sb(b.r.get_name()); bool SAME_NAME = false; - if(sa == sb) + if (sa == sb) SAME_NAME = true; return (SAME_NAME && a.r.same_chrom(b.r)); } // return true if the genomic region is null static inline bool -GenomicRegionIsNull(const GenomicRegion &gr){ +GenomicRegionIsNull(const GenomicRegion &gr) { GenomicRegion null_gr; - if(gr == null_gr) + if (gr == null_gr) return true; return false; } - static void empty_pq(GenomicRegion &prev_gr, - priority_queue, - GenomicRegionOrderChecker> &read_pq, - const string &input_file_name, - vector &counts_hist, - size_t ¤t_count) { - + std::priority_queue, + GenomicRegionOrderChecker> &read_pq, + const std::string &input_file_name, std::vector &counts_hist, + std::size_t ¤t_count) { GenomicRegion curr_gr = read_pq.top(); read_pq.pop(); // check if reads are sorted if (curr_gr.same_chrom(prev_gr) && - curr_gr.get_start() < prev_gr.get_start() - && curr_gr.get_end() < prev_gr.get_end()) { + curr_gr.get_start() < prev_gr.get_start() && + curr_gr.get_end() < prev_gr.get_end()) { std::ostringstream oss; oss << "reads unsorted in: " << input_file_name << "\n" << "prev = \t" << prev_gr << "\n" << "curr = \t" << curr_gr << "\n" << "Increase seg_len if in paired end mode"; - throw runtime_error(oss.str()); + throw std::runtime_error(oss.str()); } if (GenomicRegionIsNull(prev_gr)) current_count = 1; else { std::ostringstream oss; - bool UPDATE_HIST = - update_pe_duplicate_counts_hist(curr_gr, prev_gr, - counts_hist, current_count); + bool UPDATE_HIST = update_pe_duplicate_counts_hist( + curr_gr, prev_gr, counts_hist, current_count); if (!UPDATE_HIST) { oss << "locations unsorted in: " << input_file_name << "\n" << "prev = \t" << prev_gr << "\n" << "curr = \t" << curr_gr << "\n"; - throw runtime_error(oss.str()); + throw std::runtime_error(oss.str()); } } prev_gr = curr_gr; } - -size_t -load_counts_BAM_pe(const bool VERBOSE, - const string &input_file_name, - const size_t MAX_SEGMENT_LENGTH, - const size_t MAX_READS_TO_HOLD, - size_t &n_paired, - size_t &n_mates, - vector &counts_hist) { - - const string mapper = "general"; +[[nodiscard]] std::size_t +load_counts_BAM_pe(/*const bool VERBOSE, */ + const std::string &input_file_name, + const std::size_t MAX_SEGMENT_LENGTH, + const std::size_t MAX_READS_TO_HOLD, std::size_t &n_paired, + std::size_t &n_mates, std::vector &counts_hist) { + const std::string mapper = "general"; SAMReader_deprecated sam_reader(input_file_name, mapper); // check sam_reader - if(!sam_reader) - throw runtime_error("problem opening input file " + input_file_name); + if (!sam_reader) + throw std::runtime_error("problem opening input file " + input_file_name); SAMRecord samr; // resize vals_hist, make sure it starts out empty counts_hist.clear(); counts_hist.resize(2, 0.0); - size_t current_count = 0; - size_t suffix_len = 0; + std::size_t current_count = 0; + std::size_t suffix_len = 0; n_paired = 0; n_mates = 0; - size_t n_unpaired = 0; - size_t progress_step = 1000000; + std::size_t n_unpaired = 0; + std::size_t progress_step = 1000000; GenomicRegion prev_gr; - std::priority_queue, - GenomicRegionOrderChecker> read_pq; + std::priority_queue, + GenomicRegionOrderChecker> + read_pq; - unordered_map dangling_mates; + std::unordered_map dangling_mates; while (sam_reader >> samr) { - // only convert mapped and primary reads if (samr.is_primary && samr.is_mapped) { ++n_mates; // deal with paired-end stuff if (samr.is_mapping_paired) { + const std::size_t name_len = samr.mr.r.get_name().size() - suffix_len; + const std::string read_name(samr.mr.r.get_name().substr(0, name_len)); - const size_t name_len = samr.mr.r.get_name().size() - suffix_len; - const string read_name(samr.mr.r.get_name().substr(0, name_len)); - - if (dangling_mates.find(read_name) != dangling_mates.end()) { + if (dangling_mates.find(read_name) != std::cend(dangling_mates)) { // other end is in dangling mates, merge the two mates - if(same_read(suffix_len, samr.mr, - dangling_mates[read_name].mr)) { + if (same_read(suffix_len, samr.mr, dangling_mates[read_name].mr)) { if (samr.is_Trich) std::swap(samr, dangling_mates[read_name]); GenomicRegion merged; int len = 0; - const bool MERGE_SUCCESS = - merge_mates(suffix_len, MAX_SEGMENT_LENGTH, - dangling_mates[read_name].mr.r, samr.mr.r, - merged, len); + const bool MERGE_SUCCESS = merge_mates( + suffix_len, MAX_SEGMENT_LENGTH, dangling_mates[read_name].mr.r, + samr.mr.r, merged, len); // merge success! if (MERGE_SUCCESS && len >= 0 && len <= static_cast(MAX_SEGMENT_LENGTH)) { @@ -393,15 +433,17 @@ load_counts_BAM_pe(const bool VERBOSE, } else { // informative error message! - if (VERBOSE) { - cerr << "problem merging read " - << read_name << ", splitting read" << endl - << samr.mr << "\t" << samr.is_mapping_paired << endl - << dangling_mates[read_name].mr << "\t" - << dangling_mates[read_name].is_mapping_paired << endl - << "To merge, set max segement " - << "length (seg_len) higher." << endl; - } + // if (VERBOSE) { + std::cerr << "problem merging read " << read_name + << ", splitting read" << std::endl + << samr.mr << "\t" << samr.is_mapping_paired + << std::endl + << dangling_mates[read_name].mr << "\t" + << dangling_mates[read_name].is_mapping_paired + << std::endl + << "To merge, set max segement " + << "length (seg_len) higher." << std::endl; + // } read_pq.push(samr.mr.r); read_pq.push(dangling_mates[read_name].mr.r); n_unpaired += 2; @@ -415,7 +457,7 @@ load_counts_BAM_pe(const bool VERBOSE, n_unpaired += 2; } } - else // didn't find read in dangling_mates, store for later + else // didn't find read in dangling_mates, store for later dangling_mates[read_name] = samr; } else { @@ -423,93 +465,92 @@ load_counts_BAM_pe(const bool VERBOSE, ++n_unpaired; } - // dangling mates is too large, flush dangling_mates of reads // on different chroms and too far away - if (dangling_mates.size() > MAX_READS_TO_HOLD) { - unordered_map tmp; - for (unordered_map::iterator itr = - dangling_mates.begin(); itr != dangling_mates.end(); ++itr) { - if (itr->second.mr.r.get_chrom() != samr.mr.r.get_chrom() - || (itr->second.mr.r.get_chrom() == samr.mr.r.get_chrom() - && itr->second.mr.r.get_end() - + MAX_SEGMENT_LENGTH < samr.mr.r.get_start())) { - if(itr->second.seg_len >= 0) { + if (std::size(dangling_mates) > MAX_READS_TO_HOLD) { + std::unordered_map tmp; + for (auto itr = std::begin(dangling_mates); + itr != std::end(dangling_mates); ++itr) { + if (itr->second.mr.r.get_chrom() != samr.mr.r.get_chrom() || + (itr->second.mr.r.get_chrom() == samr.mr.r.get_chrom() && + itr->second.mr.r.get_end() + MAX_SEGMENT_LENGTH < + samr.mr.r.get_start())) { + if (itr->second.seg_len >= 0) { read_pq.push(itr->second.mr.r); ++n_unpaired; } } - else tmp[itr->first] = itr->second; + else + tmp[itr->first] = itr->second; } std::swap(tmp, dangling_mates); tmp.clear(); } - // now empty the priority queue if (!(read_pq.empty()) && is_ready_to_pop(read_pq, samr.mr.r, MAX_SEGMENT_LENGTH)) { - //begin emptying priority queue + // begin emptying priority queue while (!(read_pq.empty()) && is_ready_to_pop(read_pq, samr.mr.r, MAX_SEGMENT_LENGTH)) { - empty_pq(prev_gr, read_pq, input_file_name, counts_hist, current_count); + empty_pq(prev_gr, read_pq, input_file_name, counts_hist, + current_count); } } - if (VERBOSE && n_mates % progress_step == 0) - cerr << "Processed " << n_mates << " records" << endl; + if (/*VERBOSE && */ n_mates % progress_step == 0) + std::cerr << "Processed " << n_mates << " records" << std::endl; } } // empty dangling mates of any excess reads while (!dangling_mates.empty()) { - read_pq.push(dangling_mates.begin()->second.mr.r); - dangling_mates.erase(dangling_mates.begin()); + read_pq.push(std::begin(dangling_mates)->second.mr.r); + dangling_mates.erase(std::begin(dangling_mates)); ++n_unpaired; } - //final iteration - while(!read_pq.empty()) + // final iteration + while (!read_pq.empty()) empty_pq(prev_gr, read_pq, input_file_name, counts_hist, current_count); - if (counts_hist.size() < current_count + 1) + if (std::size(counts_hist) < current_count + 1) counts_hist.resize(current_count + 1, 0.0); ++counts_hist[current_count]; - assert((read_pq.empty())); + assert(read_pq.empty()); - size_t n_reads = n_unpaired + n_paired; + const std::size_t n_reads = n_unpaired + n_paired; - if (VERBOSE) - cerr << "paired = " << n_paired << endl - << "unpaired = " << n_unpaired << endl; + // if (VERBOSE) + std::cerr << "paired = " << n_paired << std::endl + << "unpaired = " << n_unpaired << std::endl; return n_reads; } #endif - /* this code is for BED file input */ -size_t -load_counts_BED_se(const string input_file_name, - vector &counts_hist) { +auto +load_counts_BED_se(const std::string input_file_name, + std::vector &counts_hist) -> std::size_t { // resize vals_hist counts_hist.clear(); counts_hist.resize(2, 0.0); - std::ifstream in(input_file_name.c_str()); + std::ifstream in(input_file_name); if (!in) - throw runtime_error("problem opening file: " + input_file_name); + throw std::runtime_error("problem opening file: " + input_file_name); GenomicRegion curr_gr, prev_gr; if (!(in >> prev_gr)) - throw runtime_error("problem opening file: " + input_file_name); + throw std::runtime_error("problem opening file: " + input_file_name); - size_t n_reads = 1; - size_t current_count = 1; + std::size_t n_reads = 1; + std::size_t current_count = 1; while (in >> curr_gr) { update_se_duplicate_counts_hist(curr_gr, prev_gr, input_file_name, counts_hist, current_count); @@ -518,237 +559,218 @@ load_counts_BED_se(const string input_file_name, } // to account for the last read compared to the one before it. - if(counts_hist.size() < current_count + 1) + if (std::size(counts_hist) < current_count + 1) counts_hist.resize(current_count + 1, 0.0); ++counts_hist[current_count]; return n_reads; } - -size_t -load_counts_BED_pe(const string input_file_name, - vector &counts_hist) { - +auto +load_counts_BED_pe(const std::string input_file_name, + std::vector &counts_hist) -> std::size_t { // resize vals_hist counts_hist.clear(); counts_hist.resize(2, 0.0); - std::ifstream in(input_file_name.c_str()); + std::ifstream in(input_file_name); if (!in) - throw runtime_error("problem opening file: " - + input_file_name); + throw std::runtime_error("problem opening file: " + input_file_name); GenomicRegion curr_gr, prev_gr; if (!(in >> prev_gr)) - throw runtime_error("problem opening file: " - + input_file_name); + throw std::runtime_error("problem opening file: " + input_file_name); - size_t n_reads = 1; - size_t current_count = 1; + std::size_t n_reads = 1; + std::size_t current_count = 1; - //read in file and compare each gr with the one before it + // read in file and compare each gr with the one before it while (in >> curr_gr) { - const bool UPDATE_SUCCESS = - update_pe_duplicate_counts_hist(curr_gr, prev_gr, - counts_hist, current_count); + const bool UPDATE_SUCCESS = update_pe_duplicate_counts_hist( + curr_gr, prev_gr, counts_hist, current_count); if (!UPDATE_SUCCESS) - throw runtime_error("reads unsorted in " + input_file_name); + throw std::runtime_error("reads unsorted in " + input_file_name); ++n_reads; prev_gr.swap(curr_gr); } - if (counts_hist.size() < current_count + 1) + if (std::size(counts_hist) < current_count + 1) counts_hist.resize(current_count + 1, 0.0); // to account for the last read compared to the one before it. ++counts_hist[current_count]; return n_reads; - } /* text file input */ -size_t -load_counts(const string &input_file_name, vector &counts_hist) { - +auto +load_counts(const std::string &input_file_name, + std::vector &counts_hist) -> std::size_t { std::ifstream in(input_file_name); if (!in) - throw runtime_error("problem opening file: " + input_file_name); + throw std::runtime_error("problem opening file: " + input_file_name); - size_t n_counts = 0; - string buffer; + std::size_t n_counts = 0; + std::string buffer; while (getline(in, buffer)) { - if (find(begin(buffer), end(buffer), '\r') != end(buffer)) - throw runtime_error("carriage returns in values file " - "(suggests dos or mac formatting)"); + throw std::runtime_error("carriage returns in values file " + "(suggests dos or mac formatting)"); std::istringstream iss(buffer); if (iss.good()) { double val; iss >> val; if (val > 0) { - const size_t count = static_cast(val); + const auto count = static_cast(val); // histogram is too small, resize - if (counts_hist.size() < count + 1) + if (std::size(counts_hist) < count + 1) counts_hist.resize(count + 1, 0.0); ++counts_hist[count]; n_counts += count; } else if (val != 0) - throw runtime_error("problem reading file at line " - + toa(n_counts + 1)); + throw std::runtime_error("problem reading file at line " + + toa(n_counts + 1)); } in.peek(); } return n_counts; } - -//returns number of reads from file containing counts histogram -size_t -load_histogram(const string &filename, vector &counts_hist) { - +// returns number of reads from file containing counts histogram +auto +load_histogram(const std::string &filename, + std::vector &counts_hist) -> std::size_t { counts_hist.clear(); - std::ifstream in(filename.c_str()); - if (!in) //if file doesn't open - throw runtime_error("could not open histogram: " + filename); + std::ifstream in(filename); + if (!in) // if file doesn't open + throw std::runtime_error("could not open histogram: " + filename); - size_t n_reads = 0; - size_t line_count = 0ul, prev_read_count = 0ul; - string buffer; + std::size_t n_reads = 0; + std::size_t line_count = 0ul, prev_read_count = 0ul; + std::string buffer; while (getline(in, buffer)) { - if (find(begin(buffer), end(buffer), '\r') != end(buffer)) - throw runtime_error("carriage returns in histogram file " - "(suggests dos or mac formatting)"); + throw std::runtime_error("carriage returns in histogram file " + "(suggests dos or mac formatting)"); ++line_count; - size_t read_count = 0ul; + std::size_t read_count = 0ul; double frequency = 0.0; std::istringstream is(buffer); // error reading input if (!(is >> read_count >> frequency)) - throw runtime_error("bad histogram line format:\n" + buffer + "\n" + - "(line " + toa(line_count) + ")"); + throw std::runtime_error("bad histogram line format:\n" + buffer + "\n" + + "(line " + toa(line_count) + ")"); // histogram is out of order? if (read_count < prev_read_count) - throw runtime_error("bad line order in file " + filename + "\n" + - "(line " + toa(line_count) + ")"); + throw std::runtime_error("bad line order in file " + filename + "\n" + + "(line " + toa(line_count) + ")"); counts_hist.resize(read_count + 1, 0.0); counts_hist[read_count] = frequency; if (read_count == 0ul) { - throw runtime_error("counts histograms may not " - "include an entry for zero"); + throw std::runtime_error("counts histograms may not " + "include an entry for zero"); } prev_read_count = read_count; - n_reads += static_cast(read_count*frequency); + n_reads += static_cast(read_count * frequency); } return n_reads; } - ///////////////////////////////////////////////////////// // Loading coverage counts //////////////////////////////////////////////////////// - // probabilistically split genomic regions into mutiple // genomic regions of width equal to bin_size static void -SplitGenomicRegion(const GenomicRegion &inputGR, - mt19937 &generator, const size_t bin_size, - vector &outputGRs){ - +SplitGenomicRegion(const GenomicRegion &inputGR, std::mt19937 &generator, + const std::size_t bin_size, + std::vector &outputGRs) { outputGRs.clear(); GenomicRegion gr(inputGR); - double frac = static_cast(gr.get_start() % bin_size)/bin_size; - const size_t width = gr.get_width(); + double frac = static_cast(gr.get_start() % bin_size) / bin_size; + const std::size_t width = gr.get_width(); // ADS: this seems like a bunch of duplicated code just for a single // function difference - std::uniform_real_distribution dist(0.0,1.0); + std::uniform_real_distribution dist(0.0, 1.0); if (dist(generator) > frac) { - gr.set_start(std::floor(static_cast(gr.get_start())/ - bin_size)*bin_size); + gr.set_start(std::floor(static_cast(gr.get_start()) / bin_size) * + bin_size); gr.set_end(gr.get_start() + width); } else { - gr.set_start(std::ceil(static_cast(gr.get_start())/ - bin_size)*bin_size); + gr.set_start(std::ceil(static_cast(gr.get_start()) / bin_size) * + bin_size); gr.set_end(gr.get_start() + width); } - for(size_t i = 0; i < gr.get_width(); i += bin_size){ - - const size_t curr_start = gr.get_start() + i; - const size_t curr_end - = std::min(gr.get_end(), curr_start + bin_size); - frac = static_cast(curr_end - curr_start)/bin_size; + for (std::size_t i = 0; i < gr.get_width(); i += bin_size) { + const std::size_t curr_start = gr.get_start() + i; + const std::size_t curr_end = std::min(gr.get_end(), curr_start + bin_size); + frac = static_cast(curr_end - curr_start) / bin_size; - if(dist(generator) <= frac){ - GenomicRegion binned_gr(gr.get_chrom(), curr_start, - curr_start + bin_size, - gr.get_name(), gr.get_score(), - gr.get_strand()); + if (dist(generator) <= frac) { + GenomicRegion binned_gr(gr.get_chrom(), curr_start, curr_start + bin_size, + gr.get_name(), gr.get_score(), gr.get_strand()); outputGRs.push_back(binned_gr); } } } - // split a mapped read into multiple genomic regions // based on the number of bases in each static void -SplitMappedRead(const bool VERBOSE, - const MappedRead &inputMR, - mt19937 &generator, - const size_t bin_size, - vector &outputGRs){ - +SplitMappedRead(const MappedRead &inputMR, std::mt19937 &generator, + const std::size_t bin_size, + std::vector &outputGRs) { outputGRs.clear(); - size_t covered_bases = 0; - size_t read_iterator = inputMR.r.get_start(); - size_t seq_iterator = 0; - size_t total_covered_bases = 0; + std::size_t covered_bases = 0; + std::size_t read_iterator = inputMR.r.get_start(); + std::size_t seq_iterator = 0; + std::size_t total_covered_bases = 0; - while (seq_iterator < inputMR.seq.size()) { + while (seq_iterator < std::size(inputMR.seq)) { if (inputMR.seq[seq_iterator] != 'N') - covered_bases++; + ++covered_bases; // if we reach the end of a bin, probabilistically create a binned read // with probability proportional to the number of covered bases if (read_iterator % bin_size == bin_size - 1) { - const double frac = static_cast(covered_bases)/bin_size; - std::uniform_real_distribution dist(0.0,1.0); + const double frac = static_cast(covered_bases) / bin_size; + std::uniform_real_distribution dist(0.0, 1.0); if (dist(generator) <= frac) { - const size_t curr_start = read_iterator - (read_iterator % bin_size); - const size_t curr_end = curr_start + bin_size; - const GenomicRegion binned_gr(inputMR.r.get_chrom(), curr_start, curr_end, - inputMR.r.get_name(), inputMR.r.get_score(), - inputMR.r.get_strand()); + const std::size_t curr_start = + read_iterator - (read_iterator % bin_size); + const std::size_t curr_end = curr_start + bin_size; + const GenomicRegion binned_gr( + inputMR.r.get_chrom(), curr_start, curr_end, inputMR.r.get_name(), + inputMR.r.get_score(), inputMR.r.get_strand()); outputGRs.push_back(binned_gr); } total_covered_bases += covered_bases; covered_bases = 0; } - seq_iterator++; - read_iterator++; + ++seq_iterator; + ++read_iterator; } - const double frac = static_cast(covered_bases)/bin_size; - std::uniform_real_distribution dist(0.0,1.0); + const double frac = static_cast(covered_bases) / bin_size; + std::uniform_real_distribution dist(0.0, 1.0); if (dist(generator) <= frac) { - const size_t curr_start = read_iterator - (read_iterator % bin_size); - const size_t curr_end = curr_start + bin_size; + const std::size_t curr_start = read_iterator - (read_iterator % bin_size); + const std::size_t curr_end = curr_start + bin_size; const GenomicRegion binned_gr(inputMR.r.get_chrom(), curr_start, curr_end, inputMR.r.get_name(), inputMR.r.get_score(), inputMR.r.get_strand()); @@ -756,120 +778,109 @@ SplitMappedRead(const bool VERBOSE, } } - -size_t -load_coverage_counts_MR(const bool VERBOSE, - const string input_file_name, - const unsigned long int seed, - const size_t bin_size, - const size_t max_width, - vector &coverage_hist) { - - srand(time(0) + getpid()); - //Runif runif(rand()); +auto +load_coverage_counts_MR(const std::string &input_file_name, + const std::uint64_t seed, const std::size_t bin_size, + const std::size_t max_width, + std::vector &coverage_hist) -> std::size_t { + srand(time(nullptr) + getpid()); + // Runif runif(rand()); std::mt19937 generator(seed); - std::ifstream in(input_file_name.c_str()); + std::ifstream in(input_file_name); if (!in) - throw runtime_error("problem opening file: " + input_file_name); + throw std::runtime_error("problem opening file: " + input_file_name); MappedRead mr; if (!(in >> mr)) - throw runtime_error("problem reading from: " + input_file_name); + throw std::runtime_error("problem reading from: " + input_file_name); // initialize prioirty queue to reorder the split reads ReadPQ PQ; - size_t n_reads = 0; - size_t n_bins = 0; + std::size_t n_reads = 0; + std::size_t n_bins = 0; GenomicRegion curr_gr, prev_gr; - size_t current_count = 1; + std::size_t current_count = 1; do { - if (mr.r.get_width() > max_width) - throw runtime_error("Encountered read of width " + - toa(mr.r.get_width()) + - "max_width set too small"); + throw std::runtime_error("Encountered read of width " + + toa(mr.r.get_width()) + + "max_width set too small"); - vector splitGRs; - SplitMappedRead(VERBOSE, mr, generator, bin_size, splitGRs); + std::vector splitGRs; + SplitMappedRead(mr, generator, bin_size, splitGRs); - n_reads++; - n_bins += splitGRs.size(); + ++n_reads; + n_bins += std::size(splitGRs); // add split Genomic Regions to the priority queue - for (size_t i = 0; i < splitGRs.size(); i++) - PQ.push(splitGRs[i]); + for (const auto &splitGR : splitGRs) + PQ.push(splitGR); // remove Genomic Regions from the priority queue - if (splitGRs.size() > 0) + if (std::size(splitGRs) > 0) while (!PQ.empty() && is_ready_to_pop(PQ, splitGRs.back(), max_width)) - empty_pq(curr_gr, prev_gr, current_count, - coverage_hist, PQ, input_file_name); - - } - while (in >> mr); + empty_pq(curr_gr, prev_gr, current_count, coverage_hist, PQ, + input_file_name); + } while (in >> mr); // done adding reads, now spit the rest out while (!PQ.empty()) - empty_pq(curr_gr, prev_gr, current_count, - coverage_hist, PQ, input_file_name); + empty_pq(curr_gr, prev_gr, current_count, coverage_hist, PQ, + input_file_name); return n_reads; } - -size_t -load_coverage_counts_GR(const string input_file_name, - const unsigned long int seed, - const size_t bin_size, - const size_t max_width, - vector &coverage_hist) { - - srand(time(0) + getpid()); - //Runif runif(rand()); +auto +load_coverage_counts_GR(const std::string &input_file_name, + const std::uint64_t seed, const std::size_t bin_size, + const std::size_t max_width, + std::vector &coverage_hist) -> std::size_t { + srand(time(nullptr) + getpid()); + // Runif runif(rand()); std::mt19937 generator(seed); - std::ifstream in(input_file_name.c_str()); + std::ifstream in(input_file_name); if (!in) - throw "problem opening file: " + input_file_name; + throw std::runtime_error("problem opening file: " + input_file_name); GenomicRegion inputGR; if (!(in >> inputGR)) - throw "problem reading from: " + input_file_name; + throw std::runtime_error("problem reading from: " + input_file_name); // initialize prioirty queue to reorder the split reads ReadPQ PQ; // prev and current Genomic Regions to compare - GenomicRegion curr_gr, prev_gr; - size_t n_reads = 0; - size_t current_count = 1; + GenomicRegion curr_gr; + GenomicRegion prev_gr; + std::size_t n_reads = 0; + std::size_t current_count = 1; do { - - vector splitGRs; + std::vector splitGRs; SplitGenomicRegion(inputGR, generator, bin_size, splitGRs); // add split Genomic Regions to the priority queue - for(size_t i = 0; i < splitGRs.size(); i++) - PQ.push(splitGRs[i]); + for (const auto &splitGR : splitGRs) + PQ.push(splitGR); - if (splitGRs.size() > 0) { + if (std::size(splitGRs) > 0) { // remove Genomic Regions from the priority queue while (!PQ.empty() && is_ready_to_pop(PQ, splitGRs.back(), max_width)) - empty_pq(curr_gr, prev_gr, current_count, - coverage_hist, PQ, input_file_name); + empty_pq(curr_gr, prev_gr, current_count, coverage_hist, PQ, + input_file_name); } - n_reads++; - } - while (in >> inputGR); + ++n_reads; + } while (in >> inputGR); // done adding reads, now spit the rest out while (!PQ.empty()) - empty_pq(curr_gr, prev_gr, current_count, - coverage_hist, PQ, input_file_name); + empty_pq(curr_gr, prev_gr, current_count, coverage_hist, PQ, + input_file_name); return n_reads; } diff --git a/src/load_data_for_complexity.hpp b/src/load_data_for_complexity.hpp index c6e9616..c5ab938 100644 --- a/src/load_data_for_complexity.hpp +++ b/src/load_data_for_complexity.hpp @@ -1,74 +1,66 @@ -/* Copyright (C) 2014 University of Southern California and - * Andrew D. Smith and Timothy Daley +/* Copyright (C) 2013-2026 Andrew D. Smith and Timothy Daley * - * Authors: Andrew D. Smith and Timothy Daley + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #ifndef LOAD_DATA_FOR_COMPLEXITY_HPP #define LOAD_DATA_FOR_COMPLEXITY_HPP +#include +#include #include #include -#include -size_t -load_coverage_counts_MR(const bool VERBOSE, - const std::string input_file_name, - const unsigned long int seed, - const size_t bin_size, - const size_t max_width, - std::vector &coverage_hist); +auto +load_coverage_counts_MR(const std::string &input_file_name, + const std::uint64_t seed, const std::size_t bin_size, + const std::size_t max_width, + std::vector &coverage_hist) -> std::size_t; +auto +load_coverage_counts_GR(const std::string &input_file_name, + const std::uint64_t seed, const std::size_t bin_size, + const std::size_t max_width, + std::vector &coverage_hist) -> std::size_t; -size_t -load_coverage_counts_GR(const std::string input_file_name, - const unsigned long int seed, - const size_t bin_size, - const size_t max_width, - std::vector &coverage_hist); +auto +load_histogram(const std::string &filename, + std::vector &counts_hist) -> std::size_t; +auto +load_counts(const std::string &input_file_name, + std::vector &counts_hist) -> std::size_t; -size_t -load_histogram(const std::string &filename, std::vector &counts_hist); - -size_t -load_counts(const std::string &input_file_name, std::vector &counts_hist); - -size_t +auto load_counts_BED_pe(const std::string input_file_name, - std::vector &counts_hist); + std::vector &counts_hist) -> std::size_t; -size_t +auto load_counts_BED_se(const std::string input_file_name, - std::vector &counts_hist); + std::vector &counts_hist) -> std::size_t; #ifdef HAVE_HTSLIB -size_t -load_counts_BAM_pe(const bool VERBOSE, - const std::string &input_file_name, - const size_t MAX_SEGMENT_LENGTH, - const size_t MAX_READS_TO_HOLD, - size_t &n_paired, - size_t &n_mates, - std::vector &counts_hist); +auto +load_counts_BAM_pe(const std::string &input_file_name, + const std::size_t MAX_SEGMENT_LENGTH, + const std::size_t MAX_READS_TO_HOLD, std::size_t &n_paired, + std::size_t &n_mates, + std::vector &counts_hist) -> std::size_t; -size_t +auto load_counts_BAM_se(const std::string &input_file_name, - std::vector &counts_hist); -#endif // HAVE_HTSLIB - + std::vector &counts_hist) -> std::size_t; +#endif // HAVE_HTSLIB #endif From 6986f808113690afa28bb36319349cef1382cd67 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 17:15:13 -0700 Subject: [PATCH 012/157] src/continued_fraction.hpp: forgotten includes --- src/continued_fraction.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/continued_fraction.hpp b/src/continued_fraction.hpp index 8753657..7dd28b5 100644 --- a/src/continued_fraction.hpp +++ b/src/continued_fraction.hpp @@ -18,7 +18,9 @@ #define CONTINUED_FRACTION_HPP #include +#include #include +#include #include struct ContinuedFraction { From cbdd8753b2b876d574a09b28c88612792ff3326d Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Mon, 17 Aug 2026 17:18:47 -0700 Subject: [PATCH 013/157] src/CLI11: adding CLI11 --- src/CLI11/CLI11.hpp | 12216 ++++++++++++++++++++++++++++++++++++++++ src/CLI11/CPPLINT.cfg | 1 + src/CLI11/LICENSE | 25 + 3 files changed, 12242 insertions(+) create mode 100644 src/CLI11/CLI11.hpp create mode 100644 src/CLI11/CPPLINT.cfg create mode 100644 src/CLI11/LICENSE diff --git a/src/CLI11/CLI11.hpp b/src/CLI11/CLI11.hpp new file mode 100644 index 0000000..1b90596 --- /dev/null +++ b/src/CLI11/CLI11.hpp @@ -0,0 +1,12216 @@ +// CLI11: Version 2.6.2 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.6.2 +// +// CLI11 2.6.2 Copyright (c) 2017-2026 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// Standard combined includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 6 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.6.2" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#if __cplusplus > 202002L +#define CLI11_CPP23 +#if __cplusplus > 202302L +#define CLI11_CPP26 +#endif +#endif +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard was fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#if _MSVC_LANG > 202002L && _MSC_VER >= 1922 +#define CLI11_CPP23 +#endif +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if (defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if (defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif (defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + +/** availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** availability */ +#if !defined(CLI11_CPP26) && !defined(CLI11_HAS_CODECVT) +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif +#else +#if defined(CLI11_HAS_CODECVT) +#if CLI11_HAS_CODECVT > 0 +#include +#endif +#else +#define CLI11_HAS_CODECVT 0 +#endif +#endif + +/** rtti enabled */ +#ifndef CLI11_HAS_RTTI +#if defined(__GXX_RTTI) && __GXX_RTTI == 1 +// gcc +#define CLI11_HAS_RTTI 1 +#elif defined(_CPPRTTI) && _CPPRTTI == 1 +// msvc +#define CLI11_HAS_RTTI 1 +#elif defined(__NO_RTTI__) && __NO_RTTI__ == 1 +// intel +#define CLI11_HAS_RTTI 0 +#elif defined(__has_feature) +// clang and other newer compilers +#if __has_feature(cxx_rtti) +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#elif defined(__RTTI) || defined(__INTEL_RTTI__) +// more intel and some other compilers +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + +/** Module inline to support module operations**/ +#if defined CLI11_CPP17 +#define CLI11_MODULE_INLINE inline +#else +#define CLI11_MODULE_INLINE static +#endif + + + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + + +namespace CLI { + + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + // https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number + return in << +static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +CLI11_MODULE_INLINE constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +CLI11_INLINE std::vector split(const std::string &s, char delim); + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + auto rval = s.str(); + if(!rval.empty() && delim.size() == 1 && rval.back() == delim[0]) { + // remove trailing delimiter if the last entry was empty + rval.pop_back(); + } + return rval; +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +CLI11_INLINE std::string <rim(std::string &str); + +/// Trim anything from left of string +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); + +/// Trim whitespace from right of string +CLI11_INLINE std::string &rtrim(std::string &str); + +/// Trim anything from right of string +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +CLI11_INLINE std::string &remove_quotes(std::string &str); + +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} + +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + return ((str.find_first_of('\n') == std::string::npos) && (str.find_first_of('\0') == std::string::npos)); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + return (str.empty() || (str.size() == 2 && str[0] == '%' && str[1] == '%')); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// @brief get valid group separators _' + local separator if different +/// @return a string containing the group separators +CLI11_INLINE std::string get_group_separators(); + +/// Find and replace a substring with another substring +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags); + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); + +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapable characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force = false); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, + char string_char = '\"', + char literal_char = '\'', + bool disable_secondary_array_processing = false); + +/// This function formats the given text as a paragraph with fixed width and applies correct line wrapping +/// with a custom line prefix. The paragraph will get streamed to the given ostream. +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, + const std::string &text, + std::size_t paragraphWidth, + const std::string &linePrefix = "", + bool skipPrefixOnFirstLine = false); + +} // namespace detail + + + + +namespace detail { +CLI11_INLINE std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +CLI11_INLINE std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find_first_of("\r\n", n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +CLI11_INLINE std::string get_group_separators() { + std::string separators{"_'"}; +#if CLI11_HAS_RTTI != 0 + char group_separator = std::use_facet>(std::locale()).thousands_sep(); + separators.push_back(group_separator); +#endif + return separators; +} + +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +CLI11_MODULE_INLINE const std::string &escapedChars("\b\t\n\f\r\"\\"); +CLI11_MODULE_INLINE const std::string &escapedCharsCode("btnfr\"\\"); +CLI11_MODULE_INLINE const std::string &bracketChars("\"'`[(<{"); +CLI11_MODULE_INLINE const std::string &matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: +#if defined(_MSC_VER) && _MSC_VER < 1920 + case(std::size_t)-1: +#else + case std::string::npos: +#endif + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + } else if(c == 'x' || c == 'X') { + // need to check for inadvertent binary sequences + if(!escaped_string.empty() && escaped_string.back() == '\\') { + escaped_string += std::string("\\x") + (c == 'x' ? "78" : "58"); + } else { + escaped_string.push_back(c); + } + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape || force) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string[sqLoc] = '\\'; + escaped_string.insert(sqLoc + 1, "x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE void handle_secondary_array(std::string &str) { + if(str.size() >= 2 && str.front() == '[' && str.back() == ']') { + // handle some special array processing for arguments if it might be interpreted as a secondary array + std::string tstr{"[["}; + for(std::size_t ii = 1; ii < str.size(); ++ii) { + tstr.push_back(str[ii]); + tstr.push_back(str[ii]); + } + str = std::move(tstr); + } +} + +CLI11_INLINE bool +process_quoted_string(std::string &str, char string_char, char literal_char, bool disable_secondary_array_processing) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + if(!disable_secondary_array_processing) + handle_secondary_array(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + if(!disable_secondary_array_processing) + handle_secondary_array(str); + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + if(!disable_secondary_array_processing) + handle_secondary_array(str); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + char *buffer = nullptr; + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + + // MISRA static analysis need. MISRACPP2023-25_5_2-a-1 + const char *buffer = nullptr; + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; +} + +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, + const std::string &text, + std::size_t paragraphWidth, + const std::string &linePrefix, + bool skipPrefixOnFirstLine) { + if(!skipPrefixOnFirstLine) + out << linePrefix; // First line prefix + + std::istringstream lss(text); + std::string line = ""; + while(std::getline(lss, line)) { + std::istringstream iss(line); + std::string word = ""; + std::size_t charsWritten = 0; + + while(iss >> word) { + if(charsWritten > 0 && (word.length() + 1 + charsWritten > paragraphWidth)) { + out << '\n' << linePrefix; + charsWritten = 0; + } + if(charsWritten == 0) { + out << word; + charsWritten += word.length(); + } else { + out << ' ' << word; + charsWritten += word.length() + 1; + } + } + + if(!lss.eof()) + out << '\n' << linePrefix; + } + return out; +} + +} // namespace detail + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes : int { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } + + CLI11_NODISCARD std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } + static BadNameString ReservedName(std::string name) { + return BadNameString("Names '-','--','++' are reserved and not allowed as option names " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return {"Requires at least " + std::to_string(min_option) + " options used but only " + + std::to_string(used) + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } + if(max_option == 1) + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; + + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::join(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::join(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a nonexistent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler : std::uint8_t {}; + +/// An instance to use in EnableIf +CLI11_MODULE_INLINE constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { + using type = void; +}; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { + using type = T; +}; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { + using type = std::string; +}; + +namespace adl_detail { +/// Check for existence of user-supplied lexical_cast. +/// +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). +template class is_lexical_castable { + template + static auto test(int) -> decltype(lexical_cast(std::declval(), std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; +} // namespace adl_detail + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { + using type = T; +}; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { + using type = typename element_type::type::value_type; +}; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a -Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else +#pragma diag_suppress 2361 +#endif +#endif + TT{std::declval()} +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else +#pragma diag_default 2361 +#endif +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, and an end +/// method. +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +// Even though in C++26 std::complex gains a std::tuple interface, for our purposes we treat is as NOT a tuple +template class is_tuple_like { + template ::value, detail::enabler> = detail::dummy> + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{// cppcheck-suppress unusedStructMember + std::tuple_size::type>::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + return std::string(value); // NOLINT(google-readability-casting) +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +// additional forward declarations + +/// Print tuple value string for tuples of size ==1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&value); + +/// Print tuple value string for tuples of size > 1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&value); + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template < + typename T, + enable_if_t::value && !std::is_constructible::value && + !is_ostreamable::value && !is_readable_container::type>::value && + !is_tuple_like::value, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&) { + return {}; +} + +/// convert a readable container to a string +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_readable_container::value && !is_tuple_like::value, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return {"[" + detail::join(defaults) + "]"}; +} + +/// Convert a tuple like object to a string + +/// forward declarations for tuple_value_strings +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/); + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value); + +/// Print tuple value string for tuples of size ==1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value == 1, + detail::enabler>> +inline std::string to_string(T &&value) { + return to_string(std::get<0>(value)); +} + +/// Print tuple value string for tuples of size > 1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler>> +inline std::string to_string(T &&value) { + auto tname = std::string(1, '[') + tuple_value_string(value); + tname.push_back(']'); + return tname; +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/) { + return std::string{}; +} + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value) { + auto str = std::string{to_string(std::get(value))} + ',' + tuple_value_string(value); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template +struct type_count::value && !is_complex::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template +struct type_count_min::value && !is_complex::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { + static const int value{0}; +}; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : std::uint8_t { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + std::string nstring = input; + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + } + } + return integral_conversion(nstring, output); + } + + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { + // LCOV_EXCL_START + // In some new compilers including the coverage testing one binary strings are handled properly in strtoull + // automatically so this coverage is missing but is well tested in other compilers + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + // LCOV_EXCL_STOP + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + return integral_conversion(nstring, output); + } + } + } + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { + // LCOV_EXCL_START + // In some new compilers including the coverage testing one binary strings are handled properly in strtoll + // automatically so this coverage is missing but is well tested in other compilers + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + // LCOV_EXCL_STOP + } + return false; +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret = 0; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + errno = EINVAL; + return -1; + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + std::int8_t res{0}; + // we do it this way as some systems have char as signed and not, this ensures consistency in the way things are + // handled + bool result = integral_conversion(input, res); + if(result) { + output = static_cast(res); + } + return result; +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { + output = (out > 0); + } else if(errno == ERANGE) { + output = (input[0] != '-'); + } else { + return false; + } + return true; +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + while(std::isspace(static_cast(*val))) { + ++val; + if(val == (input.c_str() + input.size())) { + return true; + } + } + + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + return lexical_cast(nstring, output); + } + } + } + return false; +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val = 0.0; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value && + is_istreamable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return from_stream(input, output); +} + +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a +/// user-supplied lexical_cast overload. +template ::value == object_category::other && !std::is_assignable::value && + !is_istreamable::value && !adl_detail::is_lexical_castable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { + static_assert(!std::is_same::value, // Can't just write false here. + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return false; +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} // LCOV_EXCL_LINE + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2{}; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } + return lexical_assign(strings[0], output); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; +} + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input, bool allow_non_standard = false); + +} // namespace detail + + + +namespace detail { + +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE std::vector split_names(std::string current) { + std::vector output; + std::size_t val = 0; + while((val = current.find(',')) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input, bool allow_non_standard) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + for(std::string name : input) { + if(name.empty()) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) { + short_names.emplace_back(1, name[1]); + } else if(name.length() > 2) { + if(allow_non_standard) { + name = name.substr(1); + if(valid_name_string(name)) { + short_names.push_back(name); + } else { + throw BadNameString::BadLongName(name); + } + } else { + throw BadNameString::MissingDash(name); + } + } else { + throw BadNameString::OneCharName(name); + } + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) { + long_names.push_back(name); + } else { + throw BadNameString::BadLongName(name); + } + } else if(name == "-" || name == "--" || name == "++") { + throw BadNameString::ReservedName(name); + } else { + if(!pos_name.empty()) { + throw BadNameString::MultiPositionalNames(name); + } + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } + } + } + return std::make_tuple(short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + /// Listing of inputs + std::vector inputs{}; + /// @brief indicator if a multiline vector separator was inserted + bool multiline{false}; + /// The list of parents and name joined by "." + CLI11_NODISCARD std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + (void)multiline; // suppression for cppcheck false positive + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + CLI11_NODISCARD std::vector from_file(const std::string &name) const { +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 + std::ifstream input{to_path(name)}; +#else + std::ifstream input{name}; +#endif + + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters and literal strings + char literalQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// comment default values + bool commentDefaultsBool = false; + /// specify the config reader should collapse repeated field names to a single vector + bool allowMultipleDuplicateFields{false}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { + stringQuote = qString; + literalQuote = literalChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// comment default value options + ConfigBase *commentDefaults(bool comDef = true) { + commentDefaultsBool = comDef; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + CLI11_NODISCARD const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + CLI11_NODISCARD int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } + /// specify that multiple duplicate arguments should be merged even if not sequential + ConfigBase *allowDuplicateFields(bool value = true) { + allowMultipleDuplicateFields = value; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + Validator(std::string validator_desc, std::function func) + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const; + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator description(std::string validator_desc) const; + + /// Generate type description information for the Validator + CLI11_NODISCARD std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + CLI11_NODISCARD const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + CLI11_NODISCARD Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + CLI11_NODISCARD Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + CLI11_NODISCARD int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + CLI11_NODISCARD bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const; + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const; + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const; + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); +}; + +/// Alias for Validator for custom Validator for clarity +using CustomValidator = Validator; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type : std::uint8_t { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +// Static is not needed here, because global const implies static. + +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator(); +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator(); +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator(); +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // namespace detail + +/// Check for existing file (returns error message if check fails) +CLI11_MODULE_INLINE const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +CLI11_MODULE_INLINE const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +CLI11_MODULE_INLINE const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +CLI11_MODULE_INLINE const detail::NonexistentPathValidator NonexistentPath; + +/// convert escaped characters into their associated values +CLI11_MODULE_INLINE const detail::EscapedStringTransformer EscapedString; + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +CLI11_MODULE_INLINE const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +CLI11_MODULE_INLINE const + Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +namespace detail { +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail +/// @} + + + + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +// The implementation of the extra validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +} // namespace detail + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string{}; + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +// Static is not needed here, because global const implies static. + +/// Check for an IP4 address +CLI11_MODULE_INLINE const detail::IPV4Validator ValidIPV4; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + // NOLINTNEXTLINE + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +} // namespace detail + /// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { + return detail::value_string(detail::pair_adaptor::second(v)); + }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options : std::uint8_t { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +#if CLI11_HAS_FILESYSTEM +namespace detail { +enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 }; +class PermissionValidator : public Validator { + public: + explicit PermissionValidator(Permission permission); +}; +} // namespace detail + +/// Check that the file exist and available for read +const detail::PermissionValidator ReadPermissions(detail::Permission::read); + +/// Check that the file exist and available for write +const detail::PermissionValidator WritePermissions(detail::Permission::write); + +/// Check that the file exist and available for write +const detail::PermissionValidator ExecPermissions(detail::Permission::exec); +#endif + +#endif + + + +namespace detail { + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto cdot = std::count(ip_addr.begin(), ip_addr.end(), '.'); + if(cdot != 3u) { + return std::string("Invalid IPV4 address: must have 3 separators"); + } + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +} // namespace detail + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail {} // namespace detail +/// @} + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE PermissionValidator::PermissionValidator(Permission permission) { + std::filesystem::perms permission_code = std::filesystem::perms::none; + std::string permission_name; + switch(permission) { + case Permission::read: + permission_code = std::filesystem::perms::owner_read | std::filesystem::perms::group_read | + std::filesystem::perms::others_read; + permission_name = "read"; + break; + case Permission::write: + permission_code = std::filesystem::perms::owner_write | std::filesystem::perms::group_write | + std::filesystem::perms::others_write; + permission_name = "write"; + break; + case Permission::exec: + permission_code = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | + std::filesystem::perms::others_exec; + permission_name = "exec"; + break; + case Permission::none: + default: + permission_code = std::filesystem::perms::none; + break; + } + func_ = [permission_code](std::string &path) { + std::error_code ec; + auto p = std::filesystem::path(path); + if(!std::filesystem::exists(p, ec)) { + return std::string("Path does not exist: ") + path; + } + if(ec) { + return std::string("Error checking path: ") + ec.message(); // LCOV_EXCL_LINE + } + if(permission_code == std::filesystem::perms::none) { + return std::string{}; + } + auto perms = std::filesystem::status(p, ec).permissions(); + if(ec) { + return std::string("Error checking path status: ") + ec.message(); // LCOV_EXCL_LINE + } + if((perms & permission_code) == std::filesystem::perms::none) { + return std::string("Path does not have required permissions: ") + path; + } + return std::string{}; + }; + description("Path with " + permission_name + " permission"); +} +#endif + +} // namespace detail +#endif + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode : std::uint8_t { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the left column (options/flags/subcommands) + std::size_t column_width_{30}; + + /// The alignment ratio for long options within the left column + float long_option_alignment_ratio_{1 / 3.f}; + + /// The width of the right column (description of options/flags/subcommands) + std::size_t right_column_width_{65}; + + /// The width of the description paragraph at the top of help + std::size_t description_paragraph_width_{80}; + + /// The width of the footer paragraph + std::size_t footer_paragraph_width_{80}; + + /// options controlling formatting for footer and descriptions + bool enable_description_formatting_{true}; + bool enable_footer_formatting_{true}; + + /// options controlling formatting of options + bool enable_option_defaults_{true}; + bool enable_option_type_names_{true}; + bool enable_default_flag_values_{true}; + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" or other labels + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the left column width (options/flags/subcommands) + void column_width(std::size_t val) { column_width_ = val; } + + /// Set the alignment ratio for long options within the left column + /// The ratio is in [0;1] range (e.g. 0.2 = 20% of column width, 6.f/column_width = 6th character) + void long_option_alignment_ratio(float ratio) { + long_option_alignment_ratio_ = + (ratio >= 0.0f) ? ((ratio <= 1.0f) ? ratio : 1.0f / ratio) : ((ratio < -1.0f) ? 1.0f / (-ratio) : -ratio); + } + + /// Set the right column width (description of options/flags/subcommands) + void right_column_width(std::size_t val) { right_column_width_ = val; } + + /// Set the description paragraph width at the top of help + void description_paragraph_width(std::size_t val) { description_paragraph_width_ = val; } + + /// Set the footer paragraph width + void footer_paragraph_width(std::size_t val) { footer_paragraph_width_ = val; } + /// enable formatting for description paragraph + void enable_description_formatting(bool value = true) { enable_description_formatting_ = value; } + /// disable formatting for footer paragraph + void enable_footer_formatting(bool value = true) { enable_footer_formatting_ = value; } + + /// enable option defaults to be printed + void enable_option_defaults(bool value = true) { enable_option_defaults_ = value; } + /// enable option type names to be printed + void enable_option_type_names(bool value = true) { enable_option_type_names_ = value; } + /// enable default flag values to be printed + void enable_default_flag_values(bool value = true) { enable_default_flag_values_ = value; } + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + CLI11_NODISCARD std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + return labels_.at(key); + } + + /// Get the current left column width (options/flags/subcommands) + CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } + + /// Get the current right column width (description of options/flags/subcommands) + CLI11_NODISCARD std::size_t get_right_column_width() const { return right_column_width_; } + + /// Get the current description paragraph width at the top of help + CLI11_NODISCARD std::size_t get_description_paragraph_width() const { return description_paragraph_width_; } + + /// Get the current footer paragraph width + CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; } + + /// @brief Get the current alignment ratio for long options within the left column + /// @return + CLI11_NODISCARD float get_long_option_alignment_ratio() const { return long_option_alignment_ratio_; } + + /// Get the current status of description paragraph formatting + CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const { return enable_description_formatting_; } + + /// Get the current status of whether footer paragraph formatting is enabled + CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const { return enable_footer_formatting_; } + + /// Get the current status of whether option defaults are printed + CLI11_NODISCARD bool is_option_defaults_enabled() const { return enable_option_defaults_; } + + /// Get the current status of whether option type names are printed + CLI11_NODISCARD bool is_option_type_names_enabled() const { return enable_option_type_names_; } + + /// Get the current status of whether default flag values are printed + CLI11_NODISCARD bool is_default_flag_values_enabled() const { return enable_default_flag_values_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + CLI11_NODISCARD virtual std::string + make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub, AppFormatMode mode) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *app, std::string, AppFormatMode mode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *, bool) const; + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; +class ConfigBase; + +using Option_p = std::unique_ptr