From 21a62ea0b0482ce217a8a32662c0e590266156dc Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Thu, 3 Jun 2021 14:32:20 +0800 Subject: [PATCH 01/83] [ci] Upload to cloudsmith --- .github/workflows/package.yml | 44 ++++++++++++++--------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index eb25fcb..3c3d647 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -24,21 +24,15 @@ jobs: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v1 - - name: Prepare recipe - run: | - set -x - cp .github/workflows/mesh_sampling.recipe /tmp/mesh_sampling.recipe - export REF=`echo ${{ github.ref }} | sed -e 's@refs/[a-z]*/@@'` - sed -i "s#@REF@#${REF}#" /tmp/mesh_sampling.recipe - echo "Prepared recipe" - echo "###############" - cat /tmp/mesh_sampling.recipe + with: + submodules: recursive - name: Build package - uses: arntanguy/github-actions/build-package@topic/cmake3.11 + uses: jrl-umi3218/github-actions/build-package-native@master with: dist: ${{ matrix.dist }} arch: ${{ matrix.arch }} - recipe: /tmp/mesh_sampling.recipe + other-mirrors: 'https://apt.kitware.com/ubuntu' + other-gpg-keys: 'DE19EB17684BA42D' - uses: actions/upload-artifact@v1 with: name: packages-${{ matrix.dist }}-${{ matrix.arch }} @@ -50,28 +44,26 @@ jobs: max-parallel: 1 fail-fast: false matrix: - dist: [bionic,focal] + dist: [xenial, bionic,focal] arch: [amd64] runs-on: ubuntu-18.04 - if: github.ref == 'refs/heads/master' && github.repository == 'arntanguy/mesh_sampling' + if: github.ref == 'refs/heads/master' steps: - name: Download packages uses: actions/download-artifact@v1 with: name: packages-${{ matrix.dist }}-${{ matrix.arch }} - - name: Upload + - name: Upload to multi-contact-head uses: jrl-umi3218/github-actions/upload-package@master with: - dist: ${{ matrix.dist }} - arch: ${{ matrix.arch }} - subject: arntanguy - repo: ppa-head - package: | - name: mesh-sampling - desc: "mesh-sampling Convert CAD models to PCL pointcloud" - licenses: [BSD 2-Clause] - vcs_url: https://github.com/arntanguy/mesh_sampling - version: 1.0.0 + dist: ubuntu/${{ matrix.dist }} + repo: mc-rtc/head + path: packages-${{ matrix.dist }}-${{ matrix.arch }} + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + - name: Upload to multi-contact-release + uses: jrl-umi3218/github-actions/upload-package@master + with: + dist: ubuntu/${{ matrix.dist }} + repo: mc-rtc/stable path: packages-${{ matrix.dist }}-${{ matrix.arch }} - BINTRAY_API_KEY: ${{ secrets.BINTRAY_API_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} From df2dcc865d7aa5bc179d0b518b87d7d2eecb475b Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Thu, 3 Jun 2021 15:46:33 +0800 Subject: [PATCH 02/83] [ci] Make the usage test internal --- .github/workflows/build.yml | 12 +++++------- .github/workflows/scripts/test-usage.sh | 12 ++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100755 .github/workflows/scripts/test-usage.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77ed47b..b4da82d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,10 +56,8 @@ jobs: compiler: ${{ matrix.compiler }} build-type: ${{ matrix.build-type }} # Checks that finding the library and building against it works - - name: Test consumption - uses: arntanguy/github-actions/build-cmake-project@topic/skip-install - with: - compiler: ${{ matrix.compiler }} - build-type: Release - project-dir: tests/test_mesh_sampling - skip-install: "true" + - name: Check usage + shell: bash + run: | + set -x + ./.github/workflows/scripts/test-usage.sh ${{ matrix.build-type }} diff --git a/.github/workflows/scripts/test-usage.sh b/.github/workflows/scripts/test-usage.sh new file mode 100755 index 0000000..e236b86 --- /dev/null +++ b/.github/workflows/scripts/test-usage.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +readonly CMAKE_BUILD_TYPE=$1 +readonly this_dir=`cd $(dirname $0); pwd` +readonly root_dir=`cd $this_dir/../../../; pwd` +readonly project_dir=$root_dir/tests/test_mesh_sampling + +mkdir -p $project_dir/build + +cd $project_dir/build +cmake ../ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} || exit 1 +cmake --build . --config ${CMAKE_BUILD_TYPE} || exit 1 From aa752d4859d1abc8d89fb42d3e9a79eaa3db09aa Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Mon, 5 Jul 2021 10:53:01 +0800 Subject: [PATCH 03/83] Do not rely on hard-coded path for the test --- tests/CMakeLists.txt | 1 + tests/testWeightedRandomSampling.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1cbefcc..c5a3857 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,7 @@ macro(mesh_sampling_test NAME) add_executable(${NAME} ${NAME}.cpp) target_link_libraries(${NAME} PUBLIC Boost::unit_test_framework Boost::disable_autolinking ${ARGN}) target_compile_definitions(${NAME} PRIVATE -DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN) + target_compile_definitions(${NAME} PRIVATE -DPROJECT_SOURCE_DIR="${PROJECT_SOURCE_DIR}") add_test(${NAME} ${NAME}) endmacro() diff --git a/tests/testWeightedRandomSampling.cpp b/tests/testWeightedRandomSampling.cpp index 12d6081..d3531ec 100644 --- a/tests/testWeightedRandomSampling.cpp +++ b/tests/testWeightedRandomSampling.cpp @@ -12,6 +12,10 @@ #include #include +#ifndef PROJECT_SOURCE_DIR +# error "PROJECT_SOURCE_DIR must be defined to compile this file" +#endif + using namespace mesh_sampling; // This is a very simple test that merely loads a sample CAD model, @@ -20,7 +24,7 @@ using namespace mesh_sampling; // after sampling is as expected BOOST_AUTO_TEST_CASE(TestWeightedRandomSampling) { - const std::string model_path = "../../sample/suzanne.obj"; + const std::string model_path = PROJECT_SOURCE_DIR "/sample/suzanne.obj"; size_t N = 1000; BOOST_REQUIRE_THROW(ASSIMPScene("wrong path.dae"), std::runtime_error); From e34c589b6d0be231d4676348ac7e14216940cfc6 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Mon, 5 Jul 2021 11:03:43 +0800 Subject: [PATCH 04/83] Replace hand-rolled randMtoN with effolkronium/random v1.4.0 --- ext/effolkronium/random.hpp | 1744 +++++++++++++++++ .../mesh_sampling/weighted_random_sampling.h | 6 - src/CMakeLists.txt | 1 + src/weighted_random_sampling.cpp | 7 +- 4 files changed, 1750 insertions(+), 8 deletions(-) create mode 100644 ext/effolkronium/random.hpp diff --git a/ext/effolkronium/random.hpp b/ext/effolkronium/random.hpp new file mode 100644 index 0000000..419401d --- /dev/null +++ b/ext/effolkronium/random.hpp @@ -0,0 +1,1744 @@ +/* +______ ___ _ _______ ________ __ +| ___ \/ _ \ | \ | | _ \ _ | \/ | Random for modern C++ +| |_/ / /_\ \| \| | | | | | | | . . | +| /| _ || . ` | | | | | | | |\/| | version 1.4.0 +| |\ \| | | || |\ | |/ /\ \_/ / | | | +\_| \_\_| |_/\_| \_/___/ \___/\_| |_/ https://github.com/effolkronium/random + +Licensed under the MIT License . +Copyright (c) 2017-2021 effolkronium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files( the "Software" ), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef EFFOLKRONIUM_RANDOM_HPP +#define EFFOLKRONIUM_RANDOM_HPP + +#include +#include // timed seed +#include +#include +#include +#include // std::forward, std::declval +#include // std::shuffle, std::next, std::distance +#include // std::begin, std::end, std::iterator_traits +#include // std::numeric_limits +#include +#include + +namespace effolkronium { + + namespace details { + /// Key type for getting common type numbers or objects + struct common{ }; + + /// True if type T is applicable by a std::uniform_int_distribution + template + struct is_uniform_int { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is applicable by a std::uniform_real_distribution + template + struct is_uniform_real { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is plain byte + template + struct is_byte { + static constexpr bool value = + std::is_same::value + || std::is_same::value; + }; + + /// True if type T is plain number type + template + struct is_supported_number { + static constexpr bool value = + is_byte ::value + || is_uniform_real::value + || is_uniform_int ::value; + }; + + /// True if type T is character type + template + struct is_supported_character { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is iterator + template + struct is_iterator { + private: + static char test( ... ); + + template ::difference_type, + typename = typename std::iterator_traits::pointer, + typename = typename std::iterator_traits::reference, + typename = typename std::iterator_traits::value_type, + typename = typename std::iterator_traits::iterator_category + > static long test( U&& ); + public: + static constexpr bool value = std::is_same< + decltype( test( std::declval( ) ) ), long>::value; + }; + + template + class has_reserve + { + private: + template + static char test(...); + + template + static long test(decltype(&C::reserve)); + public: + static constexpr bool value = std::is_same< + decltype(test(0)), long>::value; + }; + + template + class has_insert + { + private: + template + static char test(...); + + template + static long test(decltype(&C::insert)); + public: + static constexpr bool value = std::is_same< + decltype(test(0)), long>::value; + }; + + } // namespace details + + /// Default seeder for 'random' classes + struct seeder_default { + /// return seed sequence + std::seed_seq& operator() ( ) { + // MinGW issue, std::random_device returns constant value + // Use std::seed_seq with additional seed from C++ chrono + return seed_seq; + } + private: + std::seed_seq seed_seq{ { + static_cast( std::random_device{ }( ) ), + static_cast( std::chrono::steady_clock::now( ) + .time_since_epoch( ).count( ) ), + } }; + }; + + /** + * \brief Base template class for random + * with static API and static internal member storage + * \note it is NOT thread safe but more efficient then + * basic_random_thread_local + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_static { + public: + basic_random_static( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine_instance( ).discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine_instance( ).seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed( Sseq& seq ) { + engine_instance( ).seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine_instance( )( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine_instance( ) == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize( std::basic_ostream& ost ) { + ost << engine_instance( ); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize( std::basic_istream& ist ) { + ist >> engine_instance( ); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( engine_instance( ) ); + return IntegerDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( engine_instance( ) ); + return RealDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + static typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine_instance( ) ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + static typename std::enable_if< + details::has_reserve>::value + , Container>::type get(A from, A to, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + static typename std::enable_if< + !details::has_reserve>::value + , Container>::type get(A start, A end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + static typename std::enable_if< + !details::has_insert>::value + , Container>::type get(A start, A end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_insert>::value + , Container>::type get(A start, B end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( engine_instance( ) ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get( Dist& dist ) { + return dist( engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine_instance( ); + } + + /// return internal engine by ref + static Engine& engine() { + return engine_instance(); + } + protected: + /// get reference to the static engine instance + static Engine& engine_instance( ) { + static Engine engine{ Seeder{ }( ) }; + return engine; + } + }; + + /** + * \brief Base template class for random + * with thread_local API and thread_local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_thread_local { + public: + basic_random_thread_local( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine_instance( ).discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine_instance( ).seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed( Sseq& seq ) { + engine_instance( ).seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine_instance( )( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine_instance( ) == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize( std::basic_ostream& ost ) { + ost << engine_instance( ); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize( std::basic_istream& ist ) { + ist >> engine_instance( ); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( engine_instance( ) ); + return IntegerDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( engine_instance( ) ); + return RealDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + static typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine_instance( ) ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + static typename std::enable_if< + details::has_reserve>::value + , Container>::type get(A from, A to, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + static typename std::enable_if< + !details::has_reserve>::value + , Container>::type get(A start, A end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + static typename std::enable_if< + !details::has_insert>::value + , Container>::type get(A start, A end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_insert>::value + , Container>::type get(A start, B end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( engine_instance( ) ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get( Dist& dist ) { + return dist( engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine_instance( ); + } + + /// return internal engine by ref + static Engine& engine() { + return engine_instance(); + } + protected: + /// get reference to the thread local engine instance + static Engine& engine_instance( ) { + thread_local Engine engine{ Seeder{ }( ) }; + return engine; + } + }; + + /** + * \brief Base template class for random + * with local API and local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_local { + public: + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + void discard( const unsigned long long z ) { + m_engine.discard( z ); + } + + /// Reseed by Seeder + void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + void seed( const typename Engine::result_type value = + Engine::default_seed ) { + m_engine.seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + void seed( Sseq& seq ) { + m_engine.seed( seq ); + } + + /// return random number from engine in [min(), max()] range + typename Engine::result_type get( ) { + return m_engine( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + bool is_equal( const Engine& other ) { + return m_engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + void serialize( std::basic_ostream& ost ) { + ost << m_engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + void deserialize( std::basic_istream& ist ) { + ist >> m_engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( m_engine ); + return IntegerDist{ to, from }( m_engine ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( m_engine ); + return RealDist{ to, from }( m_engine ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(m_engine)); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(m_engine)); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( m_engine ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + typename std::enable_if< + details::has_reserve>::value + , Container>::type get(A from, A to, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if< + std::is_same::value + && details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + typename std::enable_if< + !details::has_reserve>::value + , Container>::type get(A start, A end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if< + std::is_same::value + && !details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + typename std::enable_if< + !details::has_insert>::value + , Container>::type get(A start, A end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if< + std::is_same::value + && !details::has_insert>::value + , Container>::type get(A start, B end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( m_engine ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + typename Dist::result_type get( Dist& dist ) { + return dist( m_engine ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, m_engine ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + Engine get_engine( ) const { + return m_engine; + } + + /// return internal engine by ref + Engine& engine() { + return m_engine; + } + protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine( ) { + // Make seeder instance for seed return by reference like std::seed_seq + return Engine{ Seeder{ }( ) }; + } + protected: + /// The random number engine + Engine m_engine{ make_seeded_engine( ) }; + }; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with static storage + * \note Not thread safe but more prefomance + */ + using random_static = basic_random_static; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with thread_local storage + * \note Thread safe but less perfomance + */ + using random_thread_local = basic_random_thread_local; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses non static methods API and data with auto storage + * \note Not thread safe. Should construct on the stack at local scope + */ + using random_local = basic_random_local; + +} // namespace effolkronium + +#endif // #ifndef EFFOLKRONIUM_RANDOM_HPP diff --git a/include/mesh_sampling/weighted_random_sampling.h b/include/mesh_sampling/weighted_random_sampling.h index 029c96c..ff9aafb 100644 --- a/include/mesh_sampling/weighted_random_sampling.h +++ b/include/mesh_sampling/weighted_random_sampling.h @@ -63,12 +63,6 @@ std::vector weighted_random_choice(const std::vector & samples return vec; } -template -T randMToN(T M, T N) -{ - return M + (static_cast(std::rand()) / (RAND_MAX / (N - M))); -} - /** * @brief Create pointcloud from MESH using weighted random sampling * diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ecfaae4..c8548de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(mesh_sampling SHARED target_link_libraries(mesh_sampling PUBLIC mesh_sampling::assimp) target_link_libraries(mesh_sampling PUBLIC mesh_sampling::PCL) target_include_directories(mesh_sampling PUBLIC $ $) +target_include_directories(mesh_sampling PRIVATE $) install(TARGETS mesh_sampling EXPORT "${TARGETS_EXPORT_NAME}" diff --git a/src/weighted_random_sampling.cpp b/src/weighted_random_sampling.cpp index 6e8fdea..477c5c0 100644 --- a/src/weighted_random_sampling.cpp +++ b/src/weighted_random_sampling.cpp @@ -4,6 +4,9 @@ #include "mesh_sampling/weighted_random_sampling.h" +#include +using Random = effolkronium::random_static; + namespace mesh_sampling { @@ -30,8 +33,8 @@ Eigen::Vector3f random_point_in_triangle(const Eigen::Vector3f & v1, const Eigen::Vector3f & v2, const Eigen::Vector3f & v3) { - float u = randMToN(0, 1); - float v = randMToN(0, 1); + float u = Random::get(0.0f, 1.0f); + float v = Random::get(0.0f, 1.0f); if(u + v > 1) { u = 1 - u; From 751ff3739e12dfa7c3e03fa08a0f2b41db6e1476 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Mon, 5 Jul 2021 11:37:26 +0800 Subject: [PATCH 05/83] Make it possible to output an stl file --- .../Findmesh_sampling_3rd_party_PCL.cmake | 2 +- src/mesh_sampling.cpp | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CMakeModules/Findmesh_sampling_3rd_party_PCL.cmake b/CMakeModules/Findmesh_sampling_3rd_party_PCL.cmake index 9e86cec..b8de2b3 100644 --- a/CMakeModules/Findmesh_sampling_3rd_party_PCL.cmake +++ b/CMakeModules/Findmesh_sampling_3rd_party_PCL.cmake @@ -16,7 +16,7 @@ if(NOT TARGET mesh_sampling::PCL) message(STATUS "Checking for module 'PCL >1.7 (common, io)'") - find_package(PCL 1.7 COMPONENTS common io QUIET) + find_package(PCL 1.7 COMPONENTS common io search surface QUIET) # On ubuntu 16.04, PCL erroneously links against a non-existant vtkproj4 target list(REMOVE_ITEM PCL_LIBRARIES "vtkproj4") if(NOT ${PCL_FOUND}) diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 1baf9ef..dc446ea 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include namespace bfs = boost::filesystem; #include namespace po = boost::program_options; @@ -41,6 +43,15 @@ void create_cloud(const aiScene * scene, unsigned N, const bfs::path & out_path, { success = mesh_sampling::io::saveQhullFile(out, *cloud); } + else if(extension == ".stl") + { + pcl::ConvexHull convex{}; + const auto shared_cloud = boost::shared_ptr>(cloud.release()); + convex.setInputCloud(shared_cloud); + pcl::PolygonMesh mesh; + convex.reconstruct(mesh); + success = pcl::io::savePolygonFileSTL(out, mesh, binary_mode); + } else { std::cerr << "Output pointcloud type " << extension << " is not supported"; @@ -63,7 +74,7 @@ int main(int argc, char ** argv) desc.add_options() ("help", "Produce this message") ("in", po::value(), "Input mesh (supported by ASSIMP)") - ("out", po::value(), "Output file (ply, pcd, qc)") + ("out", po::value(), "Output file (ply, pcd, qc, stl)") ("samples", po::value()->default_value(10000), "Number of points to sample") ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") ("binary", po::bool_switch()->default_value(false), "Outputs pointcloud in binary format (default: false)"); @@ -94,7 +105,7 @@ int main(int argc, char ** argv) std::string cloud_type = vm["type"].as(); bool cloud_binary = vm["binary"].as(); auto supported_cloud_type = std::vector{"xyz", "xyz_rgb", "xyz_normal", "xyz_rgb_normal"}; - auto supported_extensions = std::vector{".ply", ".pcd", ".qc"}; + auto supported_extensions = std::vector{".ply", ".pcd", ".qc", ".stl"}; auto check_supported = [](const std::vector & supported, const std::string & value) { if(std::find(supported.begin(), supported.end(), value) == supported.end()) @@ -134,7 +145,7 @@ int main(int argc, char ** argv) } else if(cloud_type == "xyz_normal") { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + create_cloud(mesh->scene(), N, out_p, cloud_binary); } else if(cloud_type == "xyz_rgb_normal") { From 5f4fcfd189d8baf453368b0e73091c893a1f1f38 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Mon, 5 Jul 2021 11:55:25 +0800 Subject: [PATCH 06/83] Make stl export compatible with PCL 1.7 --- src/mesh_sampling.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index dc446ea..62269f8 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -50,7 +50,11 @@ void create_cloud(const aiScene * scene, unsigned N, const bfs::path & out_path, convex.setInputCloud(shared_cloud); pcl::PolygonMesh mesh; convex.reconstruct(mesh); +#if PCL_VERSION_COMPARE(>=, 1, 8, 0) success = pcl::io::savePolygonFileSTL(out, mesh, binary_mode); +#else + success = pcl::io::savePolygonFileSTL(out, mesh); +#endif } else { From 56e28fb47f0c390d693793f414a0a10f8c5faee0 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Mon, 5 Jul 2021 12:21:13 +0800 Subject: [PATCH 07/83] [ci] Update settings - Test Debug rather than Release - Exclude clang from Ubuntu 16.04 (ABI issue) - Include Ubuntu 20.04 build --- .github/workflows/build.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4da82d..6b1893f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,9 +27,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-16.04, ubuntu-18.04] - build-type: [RelWithDebInfo, Release] + os: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] + build-type: [Debug, RelWithDebInfo] compiler: [gcc, clang] + exclude: + # ABI linking issues with stock PCL + - os: ubuntu-16.04 + compiler: clang runs-on: ${{ matrix.os }} steps: @@ -39,7 +43,7 @@ jobs: - name: Install cmake run: | wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add - - sudo apt-add-repository -y 'deb https://apt.kitware.com/ubuntu/ bionic main' + sudo apt-add-repository -y "deb https://apt.kitware.com/ubuntu/ $(lsb_release -sc) main" sudo apt-get -qq update sudo apt-get -qq install cmake cmake --version From af2ade794ef59368ef5485a4aad3ec2718e7e0ad Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 21 Jul 2021 17:32:02 +0800 Subject: [PATCH 08/83] Ensure the generated mesh has coherent normals --- src/mesh_sampling.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 62269f8..d3e0f1a 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -50,6 +50,35 @@ void create_cloud(const aiScene * scene, unsigned N, const bfs::path & out_path, convex.setInputCloud(shared_cloud); pcl::PolygonMesh mesh; convex.reconstruct(mesh); + pcl::PointCloud cloud{}; + pcl::fromPCLPointCloud2(mesh.cloud, cloud); + + // Compute the convex center + Eigen::Vector3f center = Eigen::Vector3f::Zero(); + for(auto & poly : mesh.polygons) + { + if(poly.vertices.size() != 3) + { + throw std::runtime_error("pcl::ConvexHull did not reconstruct a triangular mesh"); + } + center += (cloud[poly.vertices[0]].getVector3fMap() + cloud[poly.vertices[1]].getVector3fMap() + + cloud[poly.vertices[2]].getVector3fMap()) + / (3 * mesh.polygons.size()); + } + + // Make sure all normals point away from the mesh center + // Since the mesh is convex this is enough to ensure the normals are consistent + for(auto & poly : mesh.polygons) + { + Eigen::Vector3f p1 = cloud[poly.vertices[0]].getVector3fMap(); + Eigen::Vector3f p2 = cloud[poly.vertices[1]].getVector3fMap(); + Eigen::Vector3f p3 = cloud[poly.vertices[2]].getVector3fMap(); + Eigen::Vector3f n = (p2 - p1).cross(p3 - p1); + if(n.dot(center - p1) > 0) + { + std::swap(poly.vertices[1], poly.vertices[2]); + } + } #if PCL_VERSION_COMPARE(>=, 1, 8, 0) success = pcl::io::savePolygonFileSTL(out, mesh, binary_mode); #else From b5005f3d55a8c8bf5a15898876449c220050352a Mon Sep 17 00:00:00 2001 From: Arnaud TANGUY Date: Thu, 7 Oct 2021 15:01:11 +0200 Subject: [PATCH 09/83] Add mesh scaling option mesh_sampling --scale 0.1 ... --- include/mesh_sampling/assimp_scene.h | 36 ++++++++++++++++++---------- src/mesh_sampling.cpp | 10 +++++++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/include/mesh_sampling/assimp_scene.h b/include/mesh_sampling/assimp_scene.h index 4fbcb85..d4208e2 100644 --- a/include/mesh_sampling/assimp_scene.h +++ b/include/mesh_sampling/assimp_scene.h @@ -10,25 +10,21 @@ namespace mesh_sampling class ASSIMPScene { - // Create an instance of the Importer class // The importer will automatically delete the scene Assimp::Importer importer; const aiScene * scene_; + std::string modelPath_; public: - ASSIMPScene(const std::string & model_path) - : // And have it read the given file with some example postprocessing - // Usually - if speed is not the most important aspect for you - you'll - // propably to request more postprocessing than we do in this example. - scene_(importer.ReadFile(model_path, - aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_GenNormals - | aiProcess_FixInfacingNormals)) + ASSIMPScene(const std::string & model_path) : modelPath_(model_path) { - // If the import failed, report it - if(!scene_) - { - throw std::runtime_error(importer.GetErrorString()); - } + loadScene(); + } + + ASSIMPScene(const std::string & model_path, float scale) : modelPath_(model_path) + { + importer.SetPropertyFloat(AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, scale); + loadScene(); } /** @@ -40,6 +36,20 @@ class ASSIMPScene { return scene_; } + +protected: + void loadScene() + { + scene_ = + importer.ReadFile(modelPath_, aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_GenNormals + | aiProcess_FixInfacingNormals | aiProcess_GlobalScale); + + // If the import failed, report it + if(!scene_) + { + throw std::runtime_error(importer.GetErrorString()); + } + } }; } // namespace mesh_sampling diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index d3e0f1a..32452f6 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -109,6 +109,7 @@ int main(int argc, char ** argv) ("in", po::value(), "Input mesh (supported by ASSIMP)") ("out", po::value(), "Output file (ply, pcd, qc, stl)") ("samples", po::value()->default_value(10000), "Number of points to sample") + ("scale", po::value()->default_value(1.0), "Scale factor applied to the mesh") ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") ("binary", po::bool_switch()->default_value(false), "Outputs pointcloud in binary format (default: false)"); // clang-format on @@ -160,7 +161,14 @@ int main(int argc, char ** argv) std::unique_ptr mesh = nullptr; try { - mesh = std::unique_ptr(new ASSIMPScene(in_path)); + if(vm.count("scale")) + { + mesh = std::unique_ptr(new ASSIMPScene(in_path, vm["scale"].as())); + } + else + { + mesh = std::unique_ptr(new ASSIMPScene(in_path)); + } } catch(std::runtime_error & e) { From 04de4f3cff64ec66f1d280656f1dfbc213fe8248 Mon Sep 17 00:00:00 2001 From: Arnaud TANGUY Date: Thu, 7 Oct 2021 15:31:55 +0200 Subject: [PATCH 10/83] [ci] Drop Ubuntu 16.04 support --- .github/workflows/build.yml | 6 +---- .github/workflows/package.yml | 2 +- .../Findmesh_sampling_3rd_party_assimp.cmake | 27 +++++++------------ 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b1893f..442359d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,13 +27,9 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] + os: [ubuntu-18.04, ubuntu-20.04] build-type: [Debug, RelWithDebInfo] compiler: [gcc, clang] - exclude: - # ABI linking issues with stock PCL - - os: ubuntu-16.04 - compiler: clang runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 3c3d647..bf9363e 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - dist: [xenial, bionic, focal] + dist: [bionic, focal] arch: [amd64] runs-on: ubuntu-18.04 steps: diff --git a/CMakeModules/Findmesh_sampling_3rd_party_assimp.cmake b/CMakeModules/Findmesh_sampling_3rd_party_assimp.cmake index 58a13c7..943115c 100644 --- a/CMakeModules/Findmesh_sampling_3rd_party_assimp.cmake +++ b/CMakeModules/Findmesh_sampling_3rd_party_assimp.cmake @@ -2,29 +2,22 @@ # Copyright 2015-2019 CNRS-UM LIRMM, CNRS-AIST JRL # -# Try to find the GEOS library +# Try to find the ASSIMP library # -# In order: -# - try to find_package(geos) (upstream package) -# - try to use geos-config to find the geos installation prefix -# - try to find the geos library +# If the library is found, then you can use the mesh_sampling::assimp target # -# If the library is found, then you can use the PCL::PCL target -# -# This module will use GEOS_PREFIX as an hint to find either the geos-config -# executable or the geos library if(NOT TARGET mesh_sampling::assimp) message(STATUS "Checking for module 'ASSIMP'") find_package(assimp QUIET) - if(NOT ${ASSIMP_FOUND}) + if(NOT ${assimp_FOUND}) message(FATAL_ERROR "Could not find the ASSIMP library") - else() - message(STATUS "Found assimp library: ${ASSIMP_LIBRARIES}") - add_library(mesh_sampling::assimp INTERFACE IMPORTED) - set_target_properties(mesh_sampling::assimp PROPERTIES - #INTERFACE_INCLUDE_DIRECTORIES ${ASSIMP_INCLUDE_DIRS} - INTERFACE_LINK_LIBRARIES "${ASSIMP_LIBRARIES}" - ) endif() + + message(STATUS "Found assimp library: ${assimp_VERSION_MAJOR}.${assimp_VERSION_MINOR}.${assimp_VERSION_PATCH}") + add_library(mesh_sampling::assimp INTERFACE IMPORTED) + set_target_properties(mesh_sampling::assimp PROPERTIES + #INTERFACE_INCLUDE_DIRECTORIES ${ASSIMP_INCLUDE_DIRS} + INTERFACE_LINK_LIBRARIES "${ASSIMP_LIBRARIES}" + ) endif() From 75e5565cb68f69d9066019a0d234641b414ee188 Mon Sep 17 00:00:00 2001 From: Arnaud TANGUY Date: Mon, 11 Oct 2021 13:52:35 +0200 Subject: [PATCH 11/83] [ci] Do not generate xenial packages --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index bf9363e..1103427 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -44,7 +44,7 @@ jobs: max-parallel: 1 fail-fast: false matrix: - dist: [xenial, bionic,focal] + dist: [bionic,focal] arch: [amd64] runs-on: ubuntu-18.04 if: github.ref == 'refs/heads/master' From 64d276aa7f6212d1824a0614e6d0941a8f382a45 Mon Sep 17 00:00:00 2001 From: Arnaud Tanguy Date: Fri, 29 Oct 2021 12:47:58 +0200 Subject: [PATCH 12/83] [ci/packaging] Fix dependencies of dev package --- debian/control | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 2ad5497..d2b51eb 100644 --- a/debian/control +++ b/debian/control @@ -25,8 +25,7 @@ Depends: pkg-config, libboost-filesystem-dev, libboost-program-options-dev, libeigen3-dev (>= 3.2), - libpcl-common-dev, - libpcl-io-dev, + libpcl-dev, libassimp-dev, libproj-dev, ${misc:Depends} From b15999b0961a13c4f0f1ed6926c60bf07f8c1f64 Mon Sep 17 00:00:00 2001 From: Arnaud Tanguy Date: Mon, 8 Nov 2021 17:58:35 +0100 Subject: [PATCH 13/83] [export] Add --convert option to convert between mesh types ex: mesh_sampling --convert test.dae test.stl This does not perform any sampling, merely uses ASSIMP to perform the conversion --- include/mesh_sampling/assimp_scene.h | 69 ++++++++++++++++++++++++++-- src/CMakeLists.txt | 7 ++- src/mesh_sampling.cpp | 41 +++++++++++------ 3 files changed, 96 insertions(+), 21 deletions(-) diff --git a/include/mesh_sampling/assimp_scene.h b/include/mesh_sampling/assimp_scene.h index d4208e2..81a4f40 100644 --- a/include/mesh_sampling/assimp_scene.h +++ b/include/mesh_sampling/assimp_scene.h @@ -1,9 +1,15 @@ #pragma once -#include // C++ importer interface -#include // Post processing flags -#include // Output data structure +#include +#include + +#include +#include +#include +#include +#include #include +namespace bfs = boost::filesystem; namespace mesh_sampling { @@ -37,6 +43,63 @@ class ASSIMPScene return scene_; } + static std::vector supportedExportFormats() + { + std::vector supportedFormats; + Assimp::Exporter exporter; + auto numExporters = exporter.GetExportFormatCount(); + for(auto i = 0u; i < numExporters; i++) + { + const aiExportFormatDesc * format = exporter.GetExportFormatDescription(i); + supportedFormats.push_back(format->fileExtension); + } + return supportedFormats; + } + + /** + * @brief Export the scene to a file in a format supported by ASSIMP + * @see supportedExportFormats for a list of supported extensions + * + * @param path Export path + */ + void exportScene(const std::string & path) + { + Assimp::Exporter exporter; + auto numExporters = exporter.GetExportFormatCount(); + bfs::path out_path(path); + auto ext = boost::algorithm::to_lower_copy(out_path.extension().string()); + if(ext.empty()) + { + throw std::runtime_error("Could't export scene " + modelPath_ + " to " + path + ": invalid extension"); + } + ext.erase(0, 1); // remove leading "." + // Find exporter id for this extension + int index = -1; + for(auto i = 0u; i < numExporters; i++) + { + const aiExportFormatDesc * format = exporter.GetExportFormatDescription(i); + if(ext == format->fileExtension) + { + index = i; + break; + } + } + if(index == -1) + { // Unsupported export format + std::string s; + const auto supportedFormats = supportedExportFormats(); + std::ostringstream ss; + std::copy(supportedFormats.begin(), supportedFormats.end() - 1, std::ostream_iterator(ss, ", ")); + throw std::runtime_error("Could't export: unsupported format " + ext + "\nSupported formats are: " + ss.str()); + } + const aiExportFormatDesc * format = exporter.GetExportFormatDescription(index); + aiReturn ret = exporter.Export(scene_, format->id, path); + if(ret != AI_SUCCESS) + { + throw std::runtime_error("ASSIMP failed to export scene " + modelPath_ + " to " + path); + } + } + protected: void loadScene() { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c8548de..0c0e173 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,8 @@ add_library(mesh_sampling SHARED ../include/mesh_sampling/weighted_random_sampling.h ../include/mesh_sampling/qhull_io.h ) -target_link_libraries(mesh_sampling PUBLIC mesh_sampling::assimp) -target_link_libraries(mesh_sampling PUBLIC mesh_sampling::PCL) +target_link_libraries(mesh_sampling PUBLIC mesh_sampling::assimp mesh_sampling::PCL) +target_link_libraries(mesh_sampling PUBLIC Boost::filesystem) target_include_directories(mesh_sampling PUBLIC $ $) target_include_directories(mesh_sampling PRIVATE $) @@ -19,8 +19,7 @@ install( DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION include) add_executable(mesh_sampling_bin mesh_sampling.cpp) -target_link_libraries(mesh_sampling_bin PUBLIC mesh_sampling) -target_link_libraries(mesh_sampling_bin PUBLIC Boost::program_options Boost::filesystem) +target_link_libraries(mesh_sampling_bin PUBLIC mesh_sampling Boost::program_options) set_target_properties(mesh_sampling_bin PROPERTIES OUTPUT_NAME mesh_sampling) install(TARGETS mesh_sampling_bin diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 32452f6..536b5d9 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -111,7 +111,8 @@ int main(int argc, char ** argv) ("samples", po::value()->default_value(10000), "Number of points to sample") ("scale", po::value()->default_value(1.0), "Scale factor applied to the mesh") ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") - ("binary", po::bool_switch()->default_value(false), "Outputs pointcloud in binary format (default: false)"); + ("binary", po::bool_switch()->default_value(false), "Outputs pointcloud in binary format (default: false)") + ("convert", po::bool_switch()->default_value(false), "Convert from one mesh type to another (supported by ASSIMP)"); // clang-format on po::positional_options_description pos; @@ -129,6 +130,7 @@ int main(int argc, char ** argv) std::cout << desc << "\n"; return !vm.count("help"); } + bool convert = vm["convert"].as(); std::string in_path = vm["in"].as(); std::string out_path = vm["out"].as(); @@ -151,7 +153,11 @@ int main(int argc, char ** argv) } }; check_supported(supported_cloud_type, cloud_type); - check_supported(supported_extensions, out_extension); + + if(!convert) + { // we are sampling + check_supported(supported_extensions, out_extension); + } // Generate pointcloud // ASSIMPScene loader should be used and kept in scope for as long as the mesh @@ -176,21 +182,28 @@ int main(int argc, char ** argv) return -1; } - if(cloud_type == "xyz") - { - create_cloud(mesh->scene(), N, out_p, cloud_binary); - } - else if(cloud_type == "xyz_rgb") + if(convert) { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + mesh->exportScene(out_path); } - else if(cloud_type == "xyz_normal") - { - create_cloud(mesh->scene(), N, out_p, cloud_binary); - } - else if(cloud_type == "xyz_rgb_normal") + else { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + if(cloud_type == "xyz") + { + create_cloud(mesh->scene(), N, out_p, cloud_binary); + } + else if(cloud_type == "xyz_rgb") + { + create_cloud(mesh->scene(), N, out_p, cloud_binary); + } + else if(cloud_type == "xyz_normal") + { + create_cloud(mesh->scene(), N, out_p, cloud_binary); + } + else if(cloud_type == "xyz_rgb_normal") + { + create_cloud(mesh->scene(), N, out_p, cloud_binary); + } } return 0; From ca3a4c03d1c3cd607b2389d7319d2bae63888935 Mon Sep 17 00:00:00 2001 From: Arnaud Tanguy Date: Mon, 8 Nov 2021 19:01:35 +0100 Subject: [PATCH 14/83] [export] Supports binary export format --- include/mesh_sampling/assimp_scene.h | 59 ++++++---------------------- src/mesh_sampling.cpp | 24 +++++------ 2 files changed, 25 insertions(+), 58 deletions(-) diff --git a/include/mesh_sampling/assimp_scene.h b/include/mesh_sampling/assimp_scene.h index 81a4f40..b799186 100644 --- a/include/mesh_sampling/assimp_scene.h +++ b/include/mesh_sampling/assimp_scene.h @@ -14,14 +14,8 @@ namespace bfs = boost::filesystem; namespace mesh_sampling { -class ASSIMPScene +struct ASSIMPScene { - // The importer will automatically delete the scene - Assimp::Importer importer; - const aiScene * scene_; - std::string modelPath_; - -public: ASSIMPScene(const std::string & model_path) : modelPath_(model_path) { loadScene(); @@ -43,29 +37,13 @@ class ASSIMPScene return scene_; } - static std::vector supportedExportFormats() - { - std::vector supportedFormats; - Assimp::Exporter exporter; - auto numExporters = exporter.GetExportFormatCount(); - for(auto i = 0u; i < numExporters; i++) - { - const aiExportFormatDesc * format = exporter.GetExportFormatDescription(i); - supportedFormats.push_back(format->fileExtension); - } - return supportedFormats; - } - /** * @brief Export the scene to a file in a format supported by ASSIMP - * @see supportedExportFormats for a list of supported extensions * * @param path Export path */ - void exportScene(const std::string & path) + void exportScene(const std::string & path, const bool binary = true) { - Assimp::Exporter exporter; - auto numExporters = exporter.GetExportFormatCount(); bfs::path out_path(path); auto ext = boost::algorithm::to_lower_copy(out_path.extension().string()); if(ext.empty()) @@ -73,30 +51,13 @@ class ASSIMPScene throw std::runtime_error("Could't export scene " + modelPath_ + " to " + path + ": invalid extension"); } ext.erase(0, 1); // remove leading "." - // Find exporter id for this extension - int index = -1; - for(auto i = 0u; i < numExporters; i++) - { - const aiExportFormatDesc * format = exporter.GetExportFormatDescription(i); - if(ext == format->fileExtension) - { - index = i; - break; - } - } - if(index == -1) - { // Unsupported export format - std::string s; - const auto supportedFormats = supportedExportFormats(); - std::ostringstream ss; - std::copy(supportedFormats.begin(), supportedFormats.end() - 1, std::ostream_iterator(ss, ", ")); - throw std::runtime_error("Could't export: unsupported format " + ext + "\nSupported formats are: " + ss.str()); - } - const aiExportFormatDesc * format = exporter.GetExportFormatDescription(index); - aiReturn ret = exporter.Export(scene_, format->id, path); + if(binary) ext += "b"; // ASSIMP suffixes binary export formats with "b" + Assimp::Exporter exporter; + aiReturn ret = exporter.Export(scene_, ext, path); if(ret != AI_SUCCESS) { - throw std::runtime_error("ASSIMP failed to export scene " + modelPath_ + " to " + path); + throw std::runtime_error("ASSIMP failed to export scene " + modelPath_ + " to " + path + ": " + + exporter.GetErrorString()); } } @@ -113,6 +74,12 @@ class ASSIMPScene throw std::runtime_error(importer.GetErrorString()); } } + +protected: + // The importer will automatically delete the scene + Assimp::Importer importer; + const aiScene * scene_; + std::string modelPath_; }; } // namespace mesh_sampling diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 536b5d9..32fb61f 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -111,7 +111,7 @@ int main(int argc, char ** argv) ("samples", po::value()->default_value(10000), "Number of points to sample") ("scale", po::value()->default_value(1.0), "Scale factor applied to the mesh") ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") - ("binary", po::bool_switch()->default_value(false), "Outputs pointcloud in binary format (default: false)") + ("binary", po::bool_switch()->default_value(false), "Outputs in binary format (default: false)") ("convert", po::bool_switch()->default_value(false), "Convert from one mesh type to another (supported by ASSIMP)"); // clang-format on @@ -136,12 +136,12 @@ int main(int argc, char ** argv) std::string out_path = vm["out"].as(); bfs::path in_p(in_path); bfs::path out_p(out_path); - auto out_extension = boost::algorithm::to_lower_copy(out_p.extension().string()); - unsigned N = vm["samples"].as(); - std::string cloud_type = vm["type"].as(); - bool cloud_binary = vm["binary"].as(); - auto supported_cloud_type = std::vector{"xyz", "xyz_rgb", "xyz_normal", "xyz_rgb_normal"}; - auto supported_extensions = std::vector{".ply", ".pcd", ".qc", ".stl"}; + const auto out_extension = boost::algorithm::to_lower_copy(out_p.extension().string()); + const unsigned N = vm["samples"].as(); + const std::string cloud_type = vm["type"].as(); + const bool binary_format = vm["binary"].as(); + const auto supported_cloud_type = std::vector{"xyz", "xyz_rgb", "xyz_normal", "xyz_rgb_normal"}; + const auto supported_extensions = std::vector{".ply", ".pcd", ".qc", ".stl"}; auto check_supported = [](const std::vector & supported, const std::string & value) { if(std::find(supported.begin(), supported.end(), value) == supported.end()) @@ -184,25 +184,25 @@ int main(int argc, char ** argv) if(convert) { - mesh->exportScene(out_path); + mesh->exportScene(out_path, binary_format); } else { if(cloud_type == "xyz") { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + create_cloud(mesh->scene(), N, out_p, binary_format); } else if(cloud_type == "xyz_rgb") { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + create_cloud(mesh->scene(), N, out_p, binary_format); } else if(cloud_type == "xyz_normal") { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + create_cloud(mesh->scene(), N, out_p, binary_format); } else if(cloud_type == "xyz_rgb_normal") { - create_cloud(mesh->scene(), N, out_p, cloud_binary); + create_cloud(mesh->scene(), N, out_p, binary_format); } } From ff7a4b870f23d3a823136c7ad4f86dcc1dc62d25 Mon Sep 17 00:00:00 2001 From: Arnaud Tanguy Date: Mon, 8 Nov 2021 19:07:53 +0100 Subject: [PATCH 15/83] Remove unused dependency --- include/mesh_sampling/assimp_scene.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/mesh_sampling/assimp_scene.h b/include/mesh_sampling/assimp_scene.h index b799186..803e0a4 100644 --- a/include/mesh_sampling/assimp_scene.h +++ b/include/mesh_sampling/assimp_scene.h @@ -7,7 +7,6 @@ #include #include #include -#include #include namespace bfs = boost::filesystem; From 775f35dbd9574955dca60a3b5c14b0d03e3bdddb Mon Sep 17 00:00:00 2001 From: Arnaud TANGUY Date: Tue, 23 Aug 2022 14:49:43 +0200 Subject: [PATCH 16/83] [cmake] Fix submodule path --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 60c9164..2b7a4fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "cmake"] path = cmake - url = git://github.com/jrl-umi3218/jrl-cmakemodules.git + url = https://github.com/jrl-umi3218/jrl-cmakemodules.git From d14a56cffecc30d09242f70bdc430eb09ba66c64 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 1 Nov 2023 09:42:33 +0900 Subject: [PATCH 17/83] [ci] Refresh CI and packaging - Update clang-format-common to use clang-format-10 - Run CI on 20.04 and 22.04 - Use common and simplified workflow for packaging - Package for 22.04 --- .clang-format | 4 -- .clang-format-common.sh | 8 +-- .github/workflows/build.yml | 20 ++---- .github/workflows/mesh_sampling.recipe | 3 - .github/workflows/package.yml | 86 +++++++------------------- 5 files changed, 32 insertions(+), 89 deletions(-) delete mode 100644 .github/workflows/mesh_sampling.recipe diff --git a/.clang-format b/.clang-format index 54e2ee3..23cf2b6 100644 --- a/.clang-format +++ b/.clang-format @@ -85,10 +85,6 @@ PenaltyBreakString: 1000 PenaltyExcessCharacter: 150 PenaltyReturnTypeOnItsOwnLine: 300 PointerAlignment: Middle -RawStringFormats: - - Delimiter: pb - Language: TextProto - BasedOnStyle: google ReflowComments: true SortIncludes: true SortUsingDeclarations: true diff --git a/.clang-format-common.sh b/.clang-format-common.sh index ecb6508..bdc8bac 100644 --- a/.clang-format-common.sh +++ b/.clang-format-common.sh @@ -1,12 +1,12 @@ # This script is meant to be sourced from other scripts -# Check for clang-format, prefer 6.0 if available -if [[ -x "$(command -v clang-format-6.0)" ]]; then - clang_format=clang-format-6.0 +# Check for clang-format, prefer 10 if available +if [[ -x "$(command -v clang-format-10)" ]]; then + clang_format=clang-format-10 elif [[ -x "$(command -v clang-format)" ]]; then clang_format=clang-format else - echo "clang-format or clang-format-6.0 must be installed" + echo "clang-format or clang-format-10 must be installed" exit 1 fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 442359d..7fd2ec2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,14 +10,13 @@ on: jobs: clang-format: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 - - name: Install clang-format-6.0 + - uses: actions/checkout@v3 + - name: Install clang-format run: | sudo apt-get -qq update - sudo apt-get -qq remove clang-6.0 libclang1-6.0 libclang-common-6.0-dev libllvm6.0 - sudo apt-get -qq install clang-format-6.0 clang-format + sudo apt-get -qq install clang-format-10 - name: Run clang-format-check run: | ./.clang-format-check.sh @@ -27,22 +26,15 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-18.04, ubuntu-20.04] + os: [ubuntu-20.04, ubuntu-22.04] build-type: [Debug, RelWithDebInfo] compiler: [gcc, clang] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - - name: Install cmake - run: | - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add - - sudo apt-add-repository -y "deb https://apt.kitware.com/ubuntu/ $(lsb_release -sc) main" - sudo apt-get -qq update - sudo apt-get -qq install cmake - cmake --version - name: Install dependencies uses: jrl-umi3218/github-actions/install-dependencies@master with: diff --git a/.github/workflows/mesh_sampling.recipe b/.github/workflows/mesh_sampling.recipe deleted file mode 100644 index fcab720..0000000 --- a/.github/workflows/mesh_sampling.recipe +++ /dev/null @@ -1,3 +0,0 @@ -# git-build-recipe format 0.4 deb-version {debversion}+{time}+{git-commit} -https://github.com/arntanguy/mesh_sampling @REF@ -run git submodule update --init diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 1103427..83dd69e 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1,69 +1,27 @@ name: Package mesh_sampling - -# This workflow only runs when pushing to master or pushing a tag -# -# On master, it will: -# - Build packages for selected Debian/Ubuntu distro -# - Upload the packages to https://dl.bintray.com/arntanguy/ppa-head - on: + repository_dispatch: + types: + - package-master + - package-release push: - paths-ignore: - - ".github/workflows/build.yml" branches: - - "**" - + - "**" + tags: + - v* + pull_request: + branches: + - "**" jobs: - # This job build binary packages for Ubuntu - build-packages: - strategy: - fail-fast: false - matrix: - dist: [bionic, focal] - arch: [amd64] - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v1 - with: - submodules: recursive - - name: Build package - uses: jrl-umi3218/github-actions/build-package-native@master - with: - dist: ${{ matrix.dist }} - arch: ${{ matrix.arch }} - other-mirrors: 'https://apt.kitware.com/ubuntu' - other-gpg-keys: 'DE19EB17684BA42D' - - uses: actions/upload-artifact@v1 - with: - name: packages-${{ matrix.dist }}-${{ matrix.arch }} - path: /tmp/packages-${{ matrix.dist }}-${{ matrix.arch }}/ - # This job upload binary packages for Ubuntu - upload-packages: - needs: build-packages - strategy: - max-parallel: 1 - fail-fast: false - matrix: - dist: [bionic,focal] - arch: [amd64] - runs-on: ubuntu-18.04 - if: github.ref == 'refs/heads/master' - steps: - - name: Download packages - uses: actions/download-artifact@v1 - with: - name: packages-${{ matrix.dist }}-${{ matrix.arch }} - - name: Upload to multi-contact-head - uses: jrl-umi3218/github-actions/upload-package@master - with: - dist: ubuntu/${{ matrix.dist }} - repo: mc-rtc/head - path: packages-${{ matrix.dist }}-${{ matrix.arch }} - CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} - - name: Upload to multi-contact-release - uses: jrl-umi3218/github-actions/upload-package@master - with: - dist: ubuntu/${{ matrix.dist }} - repo: mc-rtc/stable - path: packages-${{ matrix.dist }}-${{ matrix.arch }} - CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + package: + uses: jrl-umi3218/github-actions/.github/workflows/package-project.yml@master + with: + update-stable-and-head: true + matrix: | + { + "dist": ["bionic", "focal", "jammy"], + "arch": ["amd64"] + } + secrets: + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + GH_TOKEN: ${{ secrets.GH_PAGES_TOKEN }} From 395483097bdd3e4b9f007bba307006f0eb5596a6 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 1 Nov 2023 09:53:05 +0900 Subject: [PATCH 18/83] [ci] Use latest CMake to build the package This only required for 18.04 --- .github/workflows/package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 83dd69e..e8799a9 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -16,6 +16,7 @@ jobs: package: uses: jrl-umi3218/github-actions/.github/workflows/package-project.yml@master with: + latest-cmake: true update-stable-and-head: true matrix: | { From 118bf55cf8faafe2817cef5bf55da454a9f77d68 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 1 Nov 2023 09:59:25 +0900 Subject: [PATCH 19/83] Use PCL's typedef instead of boost::shared_ptr This fixes incompatibilities between different versions of PCL --- src/mesh_sampling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 32fb61f..975f97a 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -46,7 +46,7 @@ void create_cloud(const aiScene * scene, unsigned N, const bfs::path & out_path, else if(extension == ".stl") { pcl::ConvexHull convex{}; - const auto shared_cloud = boost::shared_ptr>(cloud.release()); + const auto shared_cloud = typename pcl::PointCloud::ConstPtr(cloud.release()); convex.setInputCloud(shared_cloud); pcl::PolygonMesh mesh; convex.reconstruct(mesh); From de367cd1f7a5ce03a5470c73acf6783bfc8ec21f Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 1 Nov 2023 10:22:06 +0900 Subject: [PATCH 20/83] [cmake] Fix linking issue with MPI::MPI_C --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79c204d..360ad5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ include(cmake/base.cmake) include(cmake/eigen.cmake) search_for_eigen() -project(${PROJECT_NAME} LANGUAGES CXX VERSION ${PROJECT_VERSION}) +project(${PROJECT_NAME} LANGUAGES C CXX VERSION ${PROJECT_VERSION}) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules) # Note: From 25a42e6ae94050e8e1685f2549742fee6afed7ac Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 1 Nov 2023 11:52:23 +0900 Subject: [PATCH 21/83] [tests] Fix usage test --- tests/test_mesh_sampling/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mesh_sampling/CMakeLists.txt b/tests/test_mesh_sampling/CMakeLists.txt index e562283..4fab439 100644 --- a/tests/test_mesh_sampling/CMakeLists.txt +++ b/tests/test_mesh_sampling/CMakeLists.txt @@ -9,7 +9,7 @@ set(PROJECT_DEBUG_POSTFIX "_d") set(CXX_DISABLE_WERROR 1) set(CMAKE_CXX_STANDARD 11) -project(${PROJECT_NAME} LANGUAGES CXX VERSION ${PROJECT_VERSION}) +project(${PROJECT_NAME} LANGUAGES C CXX VERSION ${PROJECT_VERSION}) find_package(mesh_sampling REQUIRED) include_directories(include) From 295f9db12be7c1bba93489f6eed203383adc7cd0 Mon Sep 17 00:00:00 2001 From: Pierre Gergondet Date: Wed, 1 Nov 2023 11:54:23 +0900 Subject: [PATCH 22/83] [README] Update APT instructions --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3aa49c5..f1ea0dd 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,11 @@ It is provided as-is, and could probably be optimized should the need arise. Fee ## Installation -### From Ubunu packages (bionic and focal) +### From Ubunu packages (18.04, 20.04, 22.04) ```sh -# Make sure you have required tools -sudo apt install apt-transport-https lsb-release ca-certificates gnupg -# Add our key -sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key F6D3710D0B5016967A994DFFA650E12EFF6D3EDE -# Add our repository -echo "deb https://dl.bintray.com/arntanguy/ppa-head $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/arntanguy-head.list +# Setup the mirror +curl -1sLf 'https://dl.cloudsmith.io/public/mc-rtc/stable/setup.deb.sh' | sudo -E bash # Install packages sudo apt install libmesh-sampling-dev ``` From 9edc9ef4c5caea630540b780b777cfde1fcbbbf3 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Mon, 26 May 2025 11:49:02 +0900 Subject: [PATCH 23/83] git(gitignore): add entries --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7c286d9..8ea1b5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ compile_commands.json *.pyc build/ +.cache/ +.vscode/ \ No newline at end of file From 96a41ed46b16dab29c6e14d0e39dc1166c48ca3a Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Mon, 26 May 2025 13:11:59 +0900 Subject: [PATCH 24/83] feat(cmake): update CXX version to 17 | move standalone to utils folder --- CMakeLists.txt | 6 ++- utils/CMakeLists.txt | 8 ++++ utils/mesh_sampling.cpp | 95 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 utils/CMakeLists.txt create mode 100644 utils/mesh_sampling.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 360ad5e..397c95d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(PROJECT_VERSION 1.0.0) set(PROJECT_USE_CMAKE_EXPORT TRUE) set(PROJECT_DEBUG_POSTFIX "_d") set(CXX_DISABLE_WERROR 1) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) include(cmake/base.cmake) include(cmake/eigen.cmake) @@ -27,7 +27,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules) # For now, manually add those to the mesh_samplingConfig.cmake script find_package(mesh_sampling_3rd_party_assimp REQUIRED) find_package(mesh_sampling_3rd_party_PCL REQUIRED) -add_project_dependency(Boost COMPONENTS filesystem program_options REQUIRED) +add_project_dependency(Boost COMPONENTS program_options REQUIRED) # Export scripts to find the custom PCL/assimp dependencies install(FILES @@ -50,6 +50,8 @@ include(\"\$\{CMAKE_CURRENT_LIST_DIR\}/mesh_sampling_3rd_party_PCLConfig.cmake\" ) add_subdirectory(src) +add_subdirectory(utils) + if(${BUILD_TESTING}) add_subdirectory(tests) endif() diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..7d956bb --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(mesh_sampling_bin mesh_sampling.cpp) +target_link_libraries(mesh_sampling_bin PUBLIC mesh_sampling Boost::program_options) +set_target_properties(mesh_sampling_bin PROPERTIES OUTPUT_NAME mesh_sampling) + +install(TARGETS mesh_sampling_bin + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) \ No newline at end of file diff --git a/utils/mesh_sampling.cpp b/utils/mesh_sampling.cpp new file mode 100644 index 0000000..9695978 --- /dev/null +++ b/utils/mesh_sampling.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2017-2020 CNRS-UM LIRMM, CNRS-AIST JRL + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace fs = std::filesystem; +#include +namespace po = boost::program_options; + +using namespace mesh_sampling; + +int main(int argc, char ** argv) +{ + po::variables_map vm; + po::options_description desc("Allowed Options"); + + // clang-format off + desc.add_options() + ("help", "Produce this message") + ("in", po::value(), "Input mesh (supported by ASSIMP)") + ("out", po::value(), "Output file (ply, pcd, qc, stl)") + ("samples", po::value()->default_value(10000), "Number of points to sample") + ("scale", po::value()->default_value(1.0), "Scale factor applied to the mesh") + ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") + ("binary", po::bool_switch()->default_value(false), "Outputs in binary format (default: false)") + ("convert", po::bool_switch()->default_value(false), "Convert from one mesh type to another (supported by ASSIMP)"); + // clang-format on + + po::positional_options_description pos; + pos.add("in", 1); + pos.add("out", 1); + + // parse arguments and save them in the variable map (vm) + po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm); + po::notify(vm); + + if(!vm.count("in") || !vm.count("out") || vm.count("help")) + { + std::cout << "mesh_sampling is a command-line tool to sample pointcloud from CAD meshes\n\n"; + std::cout << "Usage: mesh_sampling [options] in out\n\n"; + std::cout << desc << "\n"; + return !vm.count("help"); + } + bool convert = vm["convert"].as(); + + std::string in_path = vm["in"].as(); + std::string out_path = vm["out"].as(); + fs::path in_p(in_path); + fs::path out_p(out_path); + const auto out_extension = boost::algorithm::to_lower_copy(out_p.extension().string()); + const unsigned N = vm["samples"].as(); + const std::string cloud_type = vm["type"].as(); + const bool binary_format = vm["binary"].as(); + + MeshSampling mesh_sampler(in_p); + + mesh_sampler.check_supported(supported_cloud_type, cloud_type); + + if(convert) + { + mesh_sampler.convertTo(out_p, binary_format); + } + else + { + if(cloud_type == "xyz") + { + mesh_sampler.create_cloud(N, out_p, binary_format); + } + else if(cloud_type == "xyz_rgb") + { + mesh_sampler.create_cloud(N, out_p, binary_format); + } + else if(cloud_type == "xyz_normal") + { + mesh_sampler.create_cloud(N, out_p, binary_format); + } + else if(cloud_type == "xyz_rgb_normal") + { + mesh_sampler.create_cloud(N, out_p, binary_format); + } + } + + return 0; +} From 536d3468005e4debe05d7305167c9bd5ac75b0bc Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Mon, 26 May 2025 13:13:23 +0900 Subject: [PATCH 25/83] feat(mesh_sampling): create mesh_sampling library --- include/mesh_sampling/assimp_scene.h | 6 +- include/mesh_sampling/mesh_sampling.h | 136 +++++++++++++++++ src/CMakeLists.txt | 28 ++-- src/mesh_sampling.cpp | 212 +++----------------------- tests/testWeightedRandomSampling.cpp | 13 +- tests/test_mesh_sampling/src/main.cpp | 26 +--- 6 files changed, 188 insertions(+), 233 deletions(-) create mode 100644 include/mesh_sampling/mesh_sampling.h diff --git a/include/mesh_sampling/assimp_scene.h b/include/mesh_sampling/assimp_scene.h index 803e0a4..df10b7b 100644 --- a/include/mesh_sampling/assimp_scene.h +++ b/include/mesh_sampling/assimp_scene.h @@ -1,14 +1,14 @@ #pragma once #include -#include #include #include #include #include +#include #include -namespace bfs = boost::filesystem; +namespace fs = std::filesystem; namespace mesh_sampling { @@ -43,7 +43,7 @@ struct ASSIMPScene */ void exportScene(const std::string & path, const bool binary = true) { - bfs::path out_path(path); + fs::path out_path(path); auto ext = boost::algorithm::to_lower_copy(out_path.extension().string()); if(ext.empty()) { diff --git a/include/mesh_sampling/mesh_sampling.h b/include/mesh_sampling/mesh_sampling.h new file mode 100644 index 0000000..c657c39 --- /dev/null +++ b/include/mesh_sampling/mesh_sampling.h @@ -0,0 +1,136 @@ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace mesh_sampling +{ + +const auto supported_cloud_type = std::vector{"xyz", "xyz_rgb", "xyz_normal", "xyz_rgb_normal"}; +const auto supported_extensions = std::vector{".ply", ".pcd", ".qc", ".stl"}; + +class MeshSampling +{ +public: + MeshSampling(const fs::path & in_path, float scale = 1); + + template + void create_cloud(unsigned N, const fs::path & out_path, bool binary_mode = false); + + void convertTo(const fs::path & out_path, bool binary = false); + + bool check_supported(const std::vector & supported, const std::string & value); + + /** Get mesh at index + * + * \param index mesh index (default 0) + * @return ASSIMPScene& + */ + inline ASSIMPScene & mesh(size_t index = 0) + { + if(index > meshes_.size()) + throw std::out_of_range("index > meshes.size()"); + + return *meshes_.at(index); + } + +private: + std::vector> meshes_; + float scale_; +}; + +template +void MeshSampling::create_cloud(unsigned N, const fs::path & out_path, bool binary_mode) +{ + for(const auto &mesh : meshes_) + { + WeightedRandomSampling sampler(mesh->scene()); + auto cloud = sampler.weighted_random_sampling(N); + + auto out = out_path.string(); + auto extension = boost::algorithm::to_lower_copy(out_path.extension().string()); + bool success = true; + if(extension == ".pcd") + { + success = pcl::io::savePCDFile(out, *cloud, binary_mode) == 0; + } + else if(extension == ".ply") + { + success = pcl::io::savePLYFile(out, *cloud, binary_mode) == 0; + } + else if(extension == ".qc") + { + success = mesh_sampling::io::saveQhullFile(out, *cloud); + } + else if(extension == ".stl") + { + pcl::ConvexHull convex{}; + const auto shared_cloud = typename pcl::PointCloud::ConstPtr(cloud.release()); + convex.setInputCloud(shared_cloud); + pcl::PolygonMesh mesh; + convex.reconstruct(mesh); + pcl::PointCloud cloud{}; + pcl::fromPCLPointCloud2(mesh.cloud, cloud); + + // Compute the convex center + Eigen::Vector3f center = Eigen::Vector3f::Zero(); + for(auto & poly : mesh.polygons) + { + if(poly.vertices.size() != 3) + { + throw std::runtime_error("pcl::ConvexHull did not reconstruct a triangular mesh"); + } + center += (cloud[poly.vertices[0]].getVector3fMap() + cloud[poly.vertices[1]].getVector3fMap() + + cloud[poly.vertices[2]].getVector3fMap()) + / (3 * mesh.polygons.size()); + } + + // Make sure all normals point away from the mesh center + // Since the mesh is convex this is enough to ensure the normals are consistent + for(auto & poly : mesh.polygons) + { + Eigen::Vector3f p1 = cloud[poly.vertices[0]].getVector3fMap(); + Eigen::Vector3f p2 = cloud[poly.vertices[1]].getVector3fMap(); + Eigen::Vector3f p3 = cloud[poly.vertices[2]].getVector3fMap(); + Eigen::Vector3f n = (p2 - p1).cross(p3 - p1); + if(n.dot(center - p1) > 0) + { + std::swap(poly.vertices[1], poly.vertices[2]); + } + } +#if PCL_VERSION_COMPARE(>=, 1, 8, 0) + success = pcl::io::savePolygonFileSTL(out, mesh, binary_mode); +#else + success = pcl::io::savePolygonFileSTL(out, mesh); +#endif + } + else + { + std::cerr << "Output pointcloud type " << extension << " is not supported"; + return; + } + + if(!success) + { + std::cerr << "Saving to " << out << " failed." << std::endl; + return; + } + } +} + +}; // namespace mesh_sampling \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c0e173..467cf87 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,22 @@ add_library(mesh_sampling SHARED + mesh_sampling.cpp weighted_random_sampling.cpp + ../include/mesh_sampling/mesh_sampling.h ../include/mesh_sampling/assimp_scene.h ../include/mesh_sampling/weighted_random_sampling.h ../include/mesh_sampling/qhull_io.h - ) +) + target_link_libraries(mesh_sampling PUBLIC mesh_sampling::assimp mesh_sampling::PCL) -target_link_libraries(mesh_sampling PUBLIC Boost::filesystem) -target_include_directories(mesh_sampling PUBLIC $ $) -target_include_directories(mesh_sampling PRIVATE $) + +target_include_directories(mesh_sampling PUBLIC + $ + $ +) + +target_include_directories(mesh_sampling PRIVATE + $ +) install(TARGETS mesh_sampling EXPORT "${TARGETS_EXPORT_NAME}" @@ -16,13 +25,4 @@ install(TARGETS mesh_sampling ARCHIVE DESTINATION lib) install( - DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION include) - -add_executable(mesh_sampling_bin mesh_sampling.cpp) -target_link_libraries(mesh_sampling_bin PUBLIC mesh_sampling Boost::program_options) -set_target_properties(mesh_sampling_bin PROPERTIES OUTPUT_NAME mesh_sampling) - -install(TARGETS mesh_sampling_bin - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) + DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION include) \ No newline at end of file diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 975f97a..c35baa7 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -1,210 +1,44 @@ -/* - * Copyright 2017-2020 CNRS-UM LIRMM, CNRS-AIST JRL - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -namespace bfs = boost::filesystem; -#include -namespace po = boost::program_options; +#include +#include using namespace mesh_sampling; -template -void create_cloud(const aiScene * scene, unsigned N, const bfs::path & out_path, bool binary_mode = false) -{ - WeightedRandomSampling sampler(scene); - auto cloud = sampler.weighted_random_sampling(N); - - auto out = out_path.string(); - auto extension = boost::algorithm::to_lower_copy(out_path.extension().string()); - bool success = true; - if(extension == ".pcd") - { - success = pcl::io::savePCDFile(out, *cloud, binary_mode) == 0; - } - else if(extension == ".ply") - { - success = pcl::io::savePLYFile(out, *cloud, binary_mode) == 0; - } - else if(extension == ".qc") - { - success = mesh_sampling::io::saveQhullFile(out, *cloud); - } - else if(extension == ".stl") - { - pcl::ConvexHull convex{}; - const auto shared_cloud = typename pcl::PointCloud::ConstPtr(cloud.release()); - convex.setInputCloud(shared_cloud); - pcl::PolygonMesh mesh; - convex.reconstruct(mesh); - pcl::PointCloud cloud{}; - pcl::fromPCLPointCloud2(mesh.cloud, cloud); - - // Compute the convex center - Eigen::Vector3f center = Eigen::Vector3f::Zero(); - for(auto & poly : mesh.polygons) - { - if(poly.vertices.size() != 3) - { - throw std::runtime_error("pcl::ConvexHull did not reconstruct a triangular mesh"); - } - center += (cloud[poly.vertices[0]].getVector3fMap() + cloud[poly.vertices[1]].getVector3fMap() - + cloud[poly.vertices[2]].getVector3fMap()) - / (3 * mesh.polygons.size()); - } - - // Make sure all normals point away from the mesh center - // Since the mesh is convex this is enough to ensure the normals are consistent - for(auto & poly : mesh.polygons) - { - Eigen::Vector3f p1 = cloud[poly.vertices[0]].getVector3fMap(); - Eigen::Vector3f p2 = cloud[poly.vertices[1]].getVector3fMap(); - Eigen::Vector3f p3 = cloud[poly.vertices[2]].getVector3fMap(); - Eigen::Vector3f n = (p2 - p1).cross(p3 - p1); - if(n.dot(center - p1) > 0) - { - std::swap(poly.vertices[1], poly.vertices[2]); - } - } -#if PCL_VERSION_COMPARE(>=, 1, 8, 0) - success = pcl::io::savePolygonFileSTL(out, mesh, binary_mode); -#else - success = pcl::io::savePolygonFileSTL(out, mesh); -#endif - } - else - { - std::cerr << "Output pointcloud type " << extension << " is not supported"; - exit(1); - } - - if(!success) - { - std::cerr << "Saving to " << out << " failed." << std::endl; - exit(1); - } -} - -int main(int argc, char ** argv) +MeshSampling::MeshSampling(const fs::path & in_path, float scale) : scale_(scale) { - po::variables_map vm; - po::options_description desc("Allowed Options"); - - // clang-format off - desc.add_options() - ("help", "Produce this message") - ("in", po::value(), "Input mesh (supported by ASSIMP)") - ("out", po::value(), "Output file (ply, pcd, qc, stl)") - ("samples", po::value()->default_value(10000), "Number of points to sample") - ("scale", po::value()->default_value(1.0), "Scale factor applied to the mesh") - ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") - ("binary", po::bool_switch()->default_value(false), "Outputs in binary format (default: false)") - ("convert", po::bool_switch()->default_value(false), "Convert from one mesh type to another (supported by ASSIMP)"); - // clang-format on - - po::positional_options_description pos; - pos.add("in", 1); - pos.add("out", 1); - - // parse arguments and save them in the variable map (vm) - po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm); - po::notify(vm); - - if(!vm.count("in") || !vm.count("out") || vm.count("help")) - { - std::cout << "mesh_sampling is a command-line tool to sample pointcloud from CAD meshes\n\n"; - std::cout << "Usage: mesh_sampling [options] in out\n\n"; - std::cout << desc << "\n"; - return !vm.count("help"); - } - bool convert = vm["convert"].as(); - - std::string in_path = vm["in"].as(); - std::string out_path = vm["out"].as(); - bfs::path in_p(in_path); - bfs::path out_p(out_path); - const auto out_extension = boost::algorithm::to_lower_copy(out_p.extension().string()); - const unsigned N = vm["samples"].as(); - const std::string cloud_type = vm["type"].as(); - const bool binary_format = vm["binary"].as(); - const auto supported_cloud_type = std::vector{"xyz", "xyz_rgb", "xyz_normal", "xyz_rgb_normal"}; - const auto supported_extensions = std::vector{".ply", ".pcd", ".qc", ".stl"}; - - auto check_supported = [](const std::vector & supported, const std::string & value) { - if(std::find(supported.begin(), supported.end(), value) == supported.end()) - { - std::cerr << "This program only supports: "; - std::copy(supported.begin(), supported.end(), std::ostream_iterator(std::cerr, ", ")); - std::cerr << std::endl; - exit(1); - } - }; - check_supported(supported_cloud_type, cloud_type); - - if(!convert) - { // we are sampling - check_supported(supported_extensions, out_extension); - } - - // Generate pointcloud - // ASSIMPScene loader should be used and kept in scope for as long as the mesh - // is needed. - // The default constructor loads the mesh with all the required - // post-processing according to point type (normal computation...) - std::unique_ptr mesh = nullptr; try { - if(vm.count("scale")) + if(fs::is_directory(in_path)) { - mesh = std::unique_ptr(new ASSIMPScene(in_path, vm["scale"].as())); + for(auto const & dir_entry : std::filesystem::directory_iterator{in_path}) + meshes_.push_back(std::make_unique(dir_entry.path(), scale_)); } else { - mesh = std::unique_ptr(new ASSIMPScene(in_path)); + meshes_.push_back(std::make_unique(in_path, scale_)); } } catch(std::runtime_error & e) { std::cerr << e.what() << std::endl; - return -1; + return; } +} - if(convert) +bool MeshSampling::check_supported(const std::vector & supported, const std::string & value) +{ + if(std::find(supported.begin(), supported.end(), value) == supported.end()) { - mesh->exportScene(out_path, binary_format); - } - else - { - if(cloud_type == "xyz") - { - create_cloud(mesh->scene(), N, out_p, binary_format); - } - else if(cloud_type == "xyz_rgb") - { - create_cloud(mesh->scene(), N, out_p, binary_format); - } - else if(cloud_type == "xyz_normal") - { - create_cloud(mesh->scene(), N, out_p, binary_format); - } - else if(cloud_type == "xyz_rgb_normal") - { - create_cloud(mesh->scene(), N, out_p, binary_format); - } + std::cerr << "This program only supports: "; + std::copy(supported.begin(), supported.end(), std::ostream_iterator(std::cerr, ", ")); + std::cerr << std::endl; + return false; } - return 0; + return true; } + +void MeshSampling::convertTo(const fs::path & out_path, bool binary) +{ + for(auto &mesh : meshes_) + mesh->exportScene(out_path, binary); +} \ No newline at end of file diff --git a/tests/testWeightedRandomSampling.cpp b/tests/testWeightedRandomSampling.cpp index d3531ec..2c60479 100644 --- a/tests/testWeightedRandomSampling.cpp +++ b/tests/testWeightedRandomSampling.cpp @@ -6,8 +6,7 @@ #include #include -#include -#include +#include #include #include #include @@ -28,23 +27,23 @@ BOOST_AUTO_TEST_CASE(TestWeightedRandomSampling) size_t N = 1000; BOOST_REQUIRE_THROW(ASSIMPScene("wrong path.dae"), std::runtime_error); - auto mesh = std::unique_ptr(new ASSIMPScene(model_path)); + MeshSampling sampler(model_path); std::cout << "Sampling " << N << " points from " << model_path << std::endl; - WeightedRandomSampling sampling_xyz(mesh->scene()); + WeightedRandomSampling sampling_xyz(sampler.mesh().scene()); auto cloud_xyz = sampling_xyz.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_xyz.pcd", *cloud_xyz); BOOST_REQUIRE(cloud_xyz->size() == N); - WeightedRandomSampling sampling_rgb(mesh->scene()); + WeightedRandomSampling sampling_rgb(sampler.mesh().scene()); auto cloud_rgb = sampling_rgb.weighted_random_sampling(N); BOOST_REQUIRE(cloud_rgb->size() == N); - WeightedRandomSampling sampling_normal(mesh->scene()); + WeightedRandomSampling sampling_normal(sampler.mesh().scene()); auto cloud_normal = sampling_normal.weighted_random_sampling(N); BOOST_REQUIRE(cloud_normal->size() == N); - WeightedRandomSampling sampling_rgb_normal(mesh->scene()); + WeightedRandomSampling sampling_rgb_normal(sampler.mesh().scene()); auto cloud_rgb_normal = sampling_rgb_normal.weighted_random_sampling(N); BOOST_REQUIRE(cloud_rgb_normal->size() == N); } diff --git a/tests/test_mesh_sampling/src/main.cpp b/tests/test_mesh_sampling/src/main.cpp index c56a581..fcc3913 100644 --- a/tests/test_mesh_sampling/src/main.cpp +++ b/tests/test_mesh_sampling/src/main.cpp @@ -17,8 +17,7 @@ // along with mesh_sampling. If not, see . #include -#include -#include +#include #include #include #include @@ -55,36 +54,23 @@ int main(int argc, char ** argv) help(); } - // ASSIMPScene loader should be used and kept in scope for as long as the mesh - // is needed. - // The default constructor loads the mesh with all the required - // post-processing according to point type (normal computation...) - std::unique_ptr mesh = nullptr; - try - { - mesh = std::unique_ptr(new ASSIMPScene(model_path)); - } - catch(std::runtime_error & e) - { - std::cerr << e.what() << std::endl; - return -1; - } + MeshSampling sampler(model_path); std::cout << "Sampling " << N << " points from " << model_path << std::endl; - WeightedRandomSampling sampling_xyz(mesh->scene()); + WeightedRandomSampling sampling_xyz(sampler.mesh().scene()); auto cloud_xyz = sampling_xyz.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_xyz.pcd", *cloud_xyz); std::cout << "Cloud size: " << cloud_xyz->size() << ", expected: " << N << std::endl; - WeightedRandomSampling sampling_rgb(mesh->scene()); + WeightedRandomSampling sampling_rgb(sampler.mesh().scene()); auto cloud_rgb = sampling_rgb.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_rgb.pcd", *cloud_rgb); - WeightedRandomSampling sampling_normal(mesh->scene()); + WeightedRandomSampling sampling_normal(sampler.mesh().scene()); auto cloud_normal = sampling_normal.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_normal.pcd", *cloud_normal); - WeightedRandomSampling sampling_rgb_normal(mesh->scene()); + WeightedRandomSampling sampling_rgb_normal(sampler.mesh().scene()); auto cloud_rgb_normal = sampling_rgb_normal.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_rgb_normal.pcd", *cloud_rgb_normal); } From ef14d508e00c71e12f45731fd2c8d1ee140eb935 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Mon, 26 May 2025 14:09:09 +0900 Subject: [PATCH 26/83] feat(cmake): update CXX version to 17 --- tests/test_mesh_sampling/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mesh_sampling/CMakeLists.txt b/tests/test_mesh_sampling/CMakeLists.txt index 4fab439..4624dc5 100644 --- a/tests/test_mesh_sampling/CMakeLists.txt +++ b/tests/test_mesh_sampling/CMakeLists.txt @@ -7,7 +7,7 @@ set(PROJECT_VERSION 1.0.0) set(PROJECT_USE_CMAKE_EXPORT FALSE) set(PROJECT_DEBUG_POSTFIX "_d") set(CXX_DISABLE_WERROR 1) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) project(${PROJECT_NAME} LANGUAGES C CXX VERSION ${PROJECT_VERSION}) find_package(mesh_sampling REQUIRED) From 29af5f118bf824b6e2d1986c7aa611e309ac8a2c Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Tue, 3 Jun 2025 17:58:46 +0900 Subject: [PATCH 27/83] feat(convex): generate convex files --- CMakeLists.txt | 2 + include/mesh_sampling/mesh_sampling.h | 219 ++++++++++++++++++++++---- src/CMakeLists.txt | 4 +- src/mesh_sampling.cpp | 16 +- 4 files changed, 207 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 397c95d..3b20aef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ find_package(mesh_sampling_3rd_party_assimp REQUIRED) find_package(mesh_sampling_3rd_party_PCL REQUIRED) add_project_dependency(Boost COMPONENTS program_options REQUIRED) +find_package(Qhull REQUIRED) + # Export scripts to find the custom PCL/assimp dependencies install(FILES CMakeModules/Findmesh_sampling_3rd_party_PCL.cmake diff --git a/include/mesh_sampling/mesh_sampling.h b/include/mesh_sampling/mesh_sampling.h index c657c39..eb183c0 100644 --- a/include/mesh_sampling/mesh_sampling.h +++ b/include/mesh_sampling/mesh_sampling.h @@ -1,21 +1,29 @@ -#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 +using namespace orgQhull; + namespace fs = std::filesystem; namespace mesh_sampling @@ -27,55 +35,206 @@ const auto supported_extensions = std::vector{".ply", ".pcd", ".qc" class MeshSampling { public: + MeshSampling() = default; MeshSampling(const fs::path & in_path, float scale = 1); + void load(const fs::path & in_path, float scale = 1); + + template + pcl::PointCloud create_cloud(const unsigned N); + + template + pcl::PointCloud create_cloud(const aiScene * scene, + unsigned N, + const fs::path & out_path = {}, + bool binary_mode = false); + + template + void create_clouds(unsigned N, const fs::path & out_path, const std::string & extension, bool binary_mode = false); + template - void create_cloud(unsigned N, const fs::path & out_path, bool binary_mode = false); + void create_convex(const pcl::PointCloud & cloud, const fs::path & out_path); void convertTo(const fs::path & out_path, bool binary = false); bool check_supported(const std::vector & supported, const std::string & value); - /** Get mesh at index - * - * \param index mesh index (default 0) - * @return ASSIMPScene& + /** Get mesh at index + * + * \param path mesh path + * @return ASSIMPScene& */ - inline ASSIMPScene & mesh(size_t index = 0) + inline ASSIMPScene * mesh(const std::string & path) { - if(index > meshes_.size()) - throw std::out_of_range("index > meshes.size()"); + if(meshes_.count(path) > 0) return meshes_.begin()->second.get(); - return *meshes_.at(index); + return nullptr; } private: - std::vector> meshes_; + std::map> meshes_; float scale_; }; template -void MeshSampling::create_cloud(unsigned N, const fs::path & out_path, bool binary_mode) +void MeshSampling::create_convex(const pcl::PointCloud & cloud, const fs::path & out_path) +{ + if(cloud.empty()) + { + cerr << "Error: Empty point cloud." << endl; + return; + } + + std::ofstream out(out_path); + if(!out.is_open()) + { + std::cerr << "Error: Could not open file: " << out_path << std::endl; + return; + } + + // Convert PCL cloud to a flat array for Qhull input + std::vector qhull_input; + for(const auto & pt : cloud) + { + qhull_input.push_back(pt.x); + qhull_input.push_back(pt.y); + qhull_input.push_back(pt.z); + } + + // Create Qhull object + Qhull qhull; + try + { + qhull.runQhull("pcl_input", 3, cloud.size(), qhull_input.data(), "Qt"); // 3D, triangulate option + } + catch(const std::exception & e) + { + cerr << "Qhull exception: " << e.what() << endl; + return; + } + + int dim = qhull.hullDimension(); + int numfacets = qhull.facetList().count(); + int totneighbors = numfacets * dim; // not accurate for non-simplicial facets + out << dim << "\n" << qhull.points().size() << " " << numfacets << " " << totneighbors / 2 << "\n"; + + std::vector> points; + for(QhullPoints::ConstIterator i = qhull.points().begin(); i != qhull.points().end(); ++i) + { + QhullPoint point = *i; + points.push_back(point.toStdVector()); + } + + for(const auto & point : points) + { + for(size_t i = 0; i < point.size(); ++i) + { + out << std::setw(6) << point[i] << (i < point.size() - 1 ? " " : "\n"); + } + } + + QhullFacetList facets = qhull.facetList(); + std::vector> facetVertices; + + QhullFacetListIterator j(facets); + while(j.hasNext()) + { + QhullFacet f = j.next(); + std::vector vertices; + if(!f.isGood()) + { + continue; + } + else if(!f.isTopOrient() && f.isSimplicial()) + { + QhullVertexSet vs = f.vertices(); + vertices.push_back(vs[1].point().id()); + vertices.push_back(vs[0].point().id()); + for(int i = 2; i < static_cast(vs.size()); ++i) + { + vertices.push_back(vs[i].point().id()); + } + } + else + { + QhullVertexSetIterator k(f.vertices()); + while(k.hasNext()) + { + QhullVertex vertex = k.next(); + vertices.push_back(vertex.point().id()); + } + } + facetVertices.push_back(vertices); + } + + for(const auto & vertices : facetVertices) + { + out << vertices.size() << " "; + for(int v : vertices) + { + out << v << " "; + } + out << "\n"; + } + + out.close(); + std::cout << "Convex file save to " << out_path << std::endl; +} + +template +void MeshSampling::create_clouds(unsigned N, const fs::path & out_path, const std::string & extension, bool binary_mode) { - for(const auto &mesh : meshes_) + std::string files_extension; + std::string output_path; + + if(!fs::is_directory(out_path)) { - WeightedRandomSampling sampler(mesh->scene()); - auto cloud = sampler.weighted_random_sampling(N); + std::cout << "[Warning] extension given in out_path is used by creat_clouds"; + files_extension = out_path.extension(); + } + else + { + files_extension = extension; + } + + for(const auto & mesh : meshes_) + { + std::cout << out_path.string() + mesh.first + files_extension << std::endl; + create_cloud(mesh.second, N, out_path.string() + mesh.first + extension, binary_mode); + } +} + +template +pcl::PointCloud MeshSampling::create_cloud(const unsigned N){ + WeightedRandomSampling sampler(meshes_.begin()->second->scene()); + auto cloud = sampler.weighted_random_sampling(N); + return *cloud; +} - auto out = out_path.string(); +template +pcl::PointCloud MeshSampling::create_cloud(const aiScene * scene, + const unsigned N, + const fs::path & out_path, + bool binary_mode) +{ + WeightedRandomSampling sampler(scene); + auto cloud = sampler.weighted_random_sampling(N); + + if(!out_path.empty()) + { auto extension = boost::algorithm::to_lower_copy(out_path.extension().string()); bool success = true; if(extension == ".pcd") { - success = pcl::io::savePCDFile(out, *cloud, binary_mode) == 0; + success = pcl::io::savePCDFile(out_path, *cloud, binary_mode) == 0; } else if(extension == ".ply") { - success = pcl::io::savePLYFile(out, *cloud, binary_mode) == 0; + success = pcl::io::savePLYFile(out_path, *cloud, binary_mode) == 0; } else if(extension == ".qc") { - success = mesh_sampling::io::saveQhullFile(out, *cloud); + success = mesh_sampling::io::saveQhullFile(out_path, *cloud); } else if(extension == ".stl") { @@ -114,7 +273,7 @@ void MeshSampling::create_cloud(unsigned N, const fs::path & out_path, bool bina } } #if PCL_VERSION_COMPARE(>=, 1, 8, 0) - success = pcl::io::savePolygonFileSTL(out, mesh, binary_mode); + success = pcl::io::savePolygonFileSTL(out_path, mesh, binary_mode); #else success = pcl::io::savePolygonFileSTL(out, mesh); #endif @@ -122,15 +281,21 @@ void MeshSampling::create_cloud(unsigned N, const fs::path & out_path, bool bina else { std::cerr << "Output pointcloud type " << extension << " is not supported"; - return; + return pcl::PointCloud(); } if(!success) { - std::cerr << "Saving to " << out << " failed." << std::endl; - return; + std::cerr << "Saving to " << out_path << " failed." << std::endl; + return pcl::PointCloud(); + } + else + { + std::cout << "Saving cloud to " << out_path << "success" << std::endl; } } + + return *cloud; } }; // namespace mesh_sampling \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 467cf87..0f232ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,9 @@ add_library(mesh_sampling SHARED ../include/mesh_sampling/qhull_io.h ) -target_link_libraries(mesh_sampling PUBLIC mesh_sampling::assimp mesh_sampling::PCL) +message(STATUS "--- ${QHULL_LIBRARY}") + +target_link_libraries(mesh_sampling PUBLIC mesh_sampling::assimp mesh_sampling::PCL Qhull::qhullcpp Qhull::qhull_r) target_include_directories(mesh_sampling PUBLIC $ diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index c35baa7..17cd715 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -1,20 +1,25 @@ -#include #include using namespace mesh_sampling; MeshSampling::MeshSampling(const fs::path & in_path, float scale) : scale_(scale) +{ + load(in_path, scale); +} + +void MeshSampling::load(const fs::path & in_path, float scale) { try { if(fs::is_directory(in_path)) { - for(auto const & dir_entry : std::filesystem::directory_iterator{in_path}) - meshes_.push_back(std::make_unique(dir_entry.path(), scale_)); + for(const auto & dir_entry : std::filesystem::directory_iterator{in_path}) + meshes_.insert( + std::make_pair(dir_entry.path(), std::make_shared(dir_entry.path().string(), scale))); } else { - meshes_.push_back(std::make_unique(in_path, scale_)); + meshes_.insert(std::make_pair(in_path, std::make_shared(in_path, scale))); } } catch(std::runtime_error & e) @@ -39,6 +44,5 @@ bool MeshSampling::check_supported(const std::vector & supported, c void MeshSampling::convertTo(const fs::path & out_path, bool binary) { - for(auto &mesh : meshes_) - mesh->exportScene(out_path, binary); + for(auto & mesh : meshes_) mesh.second->exportScene(out_path, binary); } \ No newline at end of file From ebaeaecd2ae1346d8e50efb8ec45229b90d63b28 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Tue, 3 Jun 2025 17:59:31 +0900 Subject: [PATCH 28/83] Consider mesh_sampling changes --- tests/testWeightedRandomSampling.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testWeightedRandomSampling.cpp b/tests/testWeightedRandomSampling.cpp index 2c60479..e281d98 100644 --- a/tests/testWeightedRandomSampling.cpp +++ b/tests/testWeightedRandomSampling.cpp @@ -30,20 +30,20 @@ BOOST_AUTO_TEST_CASE(TestWeightedRandomSampling) MeshSampling sampler(model_path); std::cout << "Sampling " << N << " points from " << model_path << std::endl; - WeightedRandomSampling sampling_xyz(sampler.mesh().scene()); + WeightedRandomSampling sampling_xyz(sampler.mesh(model_path)->scene()); auto cloud_xyz = sampling_xyz.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_xyz.pcd", *cloud_xyz); BOOST_REQUIRE(cloud_xyz->size() == N); - WeightedRandomSampling sampling_rgb(sampler.mesh().scene()); + WeightedRandomSampling sampling_rgb(sampler.mesh(model_path)->scene()); auto cloud_rgb = sampling_rgb.weighted_random_sampling(N); BOOST_REQUIRE(cloud_rgb->size() == N); - WeightedRandomSampling sampling_normal(sampler.mesh().scene()); + WeightedRandomSampling sampling_normal(sampler.mesh(model_path)->scene()); auto cloud_normal = sampling_normal.weighted_random_sampling(N); BOOST_REQUIRE(cloud_normal->size() == N); - WeightedRandomSampling sampling_rgb_normal(sampler.mesh().scene()); + WeightedRandomSampling sampling_rgb_normal(sampler.mesh(model_path)->scene()); auto cloud_rgb_normal = sampling_rgb_normal.weighted_random_sampling(N); BOOST_REQUIRE(cloud_rgb_normal->size() == N); } From 3cd0d206f7a075325fa409198776f589e94410f7 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Tue, 3 Jun 2025 18:00:04 +0900 Subject: [PATCH 29/83] feat(utils): add convex generation --- utils/mesh_sampling.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/utils/mesh_sampling.cpp b/utils/mesh_sampling.cpp index 9695978..ef062d3 100644 --- a/utils/mesh_sampling.cpp +++ b/utils/mesh_sampling.cpp @@ -75,21 +75,22 @@ int main(int argc, char ** argv) { if(cloud_type == "xyz") { - mesh_sampler.create_cloud(N, out_p, binary_format); + auto mesh = mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); + mesh_sampler.create_convex(mesh, "/tmp/test-ch.txt"); } else if(cloud_type == "xyz_rgb") { - mesh_sampler.create_cloud(N, out_p, binary_format); + mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); } else if(cloud_type == "xyz_normal") { - mesh_sampler.create_cloud(N, out_p, binary_format); + mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); } else if(cloud_type == "xyz_rgb_normal") { - mesh_sampler.create_cloud(N, out_p, binary_format); + mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); } } return 0; -} +} \ No newline at end of file From 088dfbf3ed211a057d37005482b398062169ad7a Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 15:40:59 +0900 Subject: [PATCH 30/83] style(mesh_sampling): move template implementation to .hpp --- include/mesh_sampling/mesh_sampling.h | 295 ++++++------------------ include/mesh_sampling/mesh_sampling.hpp | 253 ++++++++++++++++++++ 2 files changed, 320 insertions(+), 228 deletions(-) create mode 100644 include/mesh_sampling/mesh_sampling.hpp diff --git a/include/mesh_sampling/mesh_sampling.h b/include/mesh_sampling/mesh_sampling.h index eb183c0..b7220d5 100644 --- a/include/mesh_sampling/mesh_sampling.h +++ b/include/mesh_sampling/mesh_sampling.h @@ -1,4 +1,4 @@ - +#pragma once #include #include @@ -38,26 +38,85 @@ class MeshSampling MeshSampling() = default; MeshSampling(const fs::path & in_path, float scale = 1); + /** + * @brief Load mesh from full path or directory + * + * @param in_path path or directory + * @param scale scaling factor (default: 1) + */ void load(const fs::path & in_path, float scale = 1); + /** + * @brief Create a cloud + * + * @tparam PointT + * @param N number of sampling points (default: 2000) + * @return pcl::PointCloud + */ template - pcl::PointCloud create_cloud(const unsigned N); + pcl::PointCloud cloud(const unsigned N = 2000); + /** + * @brief Create and save a pointcloud + * + * @tparam PointT + * @param scene ASSIMPScene + * @param N number sampling points + * @param out_path if empty or directory, file is not saved (default: {}) + * @param binary_mode is binary (default: false) + * @return pcl::PointCloud + */ template pcl::PointCloud create_cloud(const aiScene * scene, unsigned N, const fs::path & out_path = {}, bool binary_mode = false); + /** + * @brief Create and save pointclouds, to be used when importing meshes from a directory. If out_path is empty clouds + * are not saved + * + * @tparam PointT + * @param N number of sampling points + * @param out_path directory to save the clouds (default: {}) + * @param extension saving extension (default: ".qc") + * @param binary_mode is binary mnode (default: false) + */ template - void create_clouds(unsigned N, const fs::path & out_path, const std::string & extension, bool binary_mode = false); + std::map> create_clouds(unsigned N, + const fs::path & out_path = {}, + const std::string & extension = ".qc", + bool binary_mode = false); + /** + * @brief Create a convex object using qhull library and save it to a file + * + * @tparam PointT + * @param cloud PCL point cloud + * @param out_path output directory + */ template void create_convex(const pcl::PointCloud & cloud, const fs::path & out_path); + template + void create_convexes(const std::map> & clouds, const fs::path & out_path); + + /** + * @brief Export all meshes to a file in a format support by ASSIMP + * + * @param out_path Export path + * @param binary is binary (defaukt:false) + */ void convertTo(const fs::path & out_path, bool binary = false); - bool check_supported(const std::vector & supported, const std::string & value); + /** + * @brief Check supported type + * + * @param type type to check + * @param supported list of supported types (default : "xyz", "xyz_rgb", "xyz_normal", "xyz_rgb_normal") + * @return bool + */ + bool check_supported(const std::string & type, const std::vector & supported = supported_cloud_type); /** Get mesh at index * @@ -72,230 +131,10 @@ class MeshSampling } private: - std::map> meshes_; - float scale_; + std::map> meshes_; // + float scale_; // scaling factor }; -template -void MeshSampling::create_convex(const pcl::PointCloud & cloud, const fs::path & out_path) -{ - if(cloud.empty()) - { - cerr << "Error: Empty point cloud." << endl; - return; - } - - std::ofstream out(out_path); - if(!out.is_open()) - { - std::cerr << "Error: Could not open file: " << out_path << std::endl; - return; - } - - // Convert PCL cloud to a flat array for Qhull input - std::vector qhull_input; - for(const auto & pt : cloud) - { - qhull_input.push_back(pt.x); - qhull_input.push_back(pt.y); - qhull_input.push_back(pt.z); - } - - // Create Qhull object - Qhull qhull; - try - { - qhull.runQhull("pcl_input", 3, cloud.size(), qhull_input.data(), "Qt"); // 3D, triangulate option - } - catch(const std::exception & e) - { - cerr << "Qhull exception: " << e.what() << endl; - return; - } - - int dim = qhull.hullDimension(); - int numfacets = qhull.facetList().count(); - int totneighbors = numfacets * dim; // not accurate for non-simplicial facets - out << dim << "\n" << qhull.points().size() << " " << numfacets << " " << totneighbors / 2 << "\n"; - - std::vector> points; - for(QhullPoints::ConstIterator i = qhull.points().begin(); i != qhull.points().end(); ++i) - { - QhullPoint point = *i; - points.push_back(point.toStdVector()); - } - - for(const auto & point : points) - { - for(size_t i = 0; i < point.size(); ++i) - { - out << std::setw(6) << point[i] << (i < point.size() - 1 ? " " : "\n"); - } - } - - QhullFacetList facets = qhull.facetList(); - std::vector> facetVertices; - - QhullFacetListIterator j(facets); - while(j.hasNext()) - { - QhullFacet f = j.next(); - std::vector vertices; - if(!f.isGood()) - { - continue; - } - else if(!f.isTopOrient() && f.isSimplicial()) - { - QhullVertexSet vs = f.vertices(); - vertices.push_back(vs[1].point().id()); - vertices.push_back(vs[0].point().id()); - for(int i = 2; i < static_cast(vs.size()); ++i) - { - vertices.push_back(vs[i].point().id()); - } - } - else - { - QhullVertexSetIterator k(f.vertices()); - while(k.hasNext()) - { - QhullVertex vertex = k.next(); - vertices.push_back(vertex.point().id()); - } - } - facetVertices.push_back(vertices); - } - - for(const auto & vertices : facetVertices) - { - out << vertices.size() << " "; - for(int v : vertices) - { - out << v << " "; - } - out << "\n"; - } - - out.close(); - std::cout << "Convex file save to " << out_path << std::endl; -} - -template -void MeshSampling::create_clouds(unsigned N, const fs::path & out_path, const std::string & extension, bool binary_mode) -{ - std::string files_extension; - std::string output_path; - - if(!fs::is_directory(out_path)) - { - std::cout << "[Warning] extension given in out_path is used by creat_clouds"; - files_extension = out_path.extension(); - } - else - { - files_extension = extension; - } - - for(const auto & mesh : meshes_) - { - std::cout << out_path.string() + mesh.first + files_extension << std::endl; - create_cloud(mesh.second, N, out_path.string() + mesh.first + extension, binary_mode); - } -} - -template -pcl::PointCloud MeshSampling::create_cloud(const unsigned N){ - WeightedRandomSampling sampler(meshes_.begin()->second->scene()); - auto cloud = sampler.weighted_random_sampling(N); - return *cloud; -} - -template -pcl::PointCloud MeshSampling::create_cloud(const aiScene * scene, - const unsigned N, - const fs::path & out_path, - bool binary_mode) -{ - WeightedRandomSampling sampler(scene); - auto cloud = sampler.weighted_random_sampling(N); - - if(!out_path.empty()) - { - auto extension = boost::algorithm::to_lower_copy(out_path.extension().string()); - bool success = true; - if(extension == ".pcd") - { - success = pcl::io::savePCDFile(out_path, *cloud, binary_mode) == 0; - } - else if(extension == ".ply") - { - success = pcl::io::savePLYFile(out_path, *cloud, binary_mode) == 0; - } - else if(extension == ".qc") - { - success = mesh_sampling::io::saveQhullFile(out_path, *cloud); - } - else if(extension == ".stl") - { - pcl::ConvexHull convex{}; - const auto shared_cloud = typename pcl::PointCloud::ConstPtr(cloud.release()); - convex.setInputCloud(shared_cloud); - pcl::PolygonMesh mesh; - convex.reconstruct(mesh); - pcl::PointCloud cloud{}; - pcl::fromPCLPointCloud2(mesh.cloud, cloud); - - // Compute the convex center - Eigen::Vector3f center = Eigen::Vector3f::Zero(); - for(auto & poly : mesh.polygons) - { - if(poly.vertices.size() != 3) - { - throw std::runtime_error("pcl::ConvexHull did not reconstruct a triangular mesh"); - } - center += (cloud[poly.vertices[0]].getVector3fMap() + cloud[poly.vertices[1]].getVector3fMap() - + cloud[poly.vertices[2]].getVector3fMap()) - / (3 * mesh.polygons.size()); - } - - // Make sure all normals point away from the mesh center - // Since the mesh is convex this is enough to ensure the normals are consistent - for(auto & poly : mesh.polygons) - { - Eigen::Vector3f p1 = cloud[poly.vertices[0]].getVector3fMap(); - Eigen::Vector3f p2 = cloud[poly.vertices[1]].getVector3fMap(); - Eigen::Vector3f p3 = cloud[poly.vertices[2]].getVector3fMap(); - Eigen::Vector3f n = (p2 - p1).cross(p3 - p1); - if(n.dot(center - p1) > 0) - { - std::swap(poly.vertices[1], poly.vertices[2]); - } - } -#if PCL_VERSION_COMPARE(>=, 1, 8, 0) - success = pcl::io::savePolygonFileSTL(out_path, mesh, binary_mode); -#else - success = pcl::io::savePolygonFileSTL(out, mesh); -#endif - } - else - { - std::cerr << "Output pointcloud type " << extension << " is not supported"; - return pcl::PointCloud(); - } - - if(!success) - { - std::cerr << "Saving to " << out_path << " failed." << std::endl; - return pcl::PointCloud(); - } - else - { - std::cout << "Saving cloud to " << out_path << "success" << std::endl; - } - } - - return *cloud; -} +}; // namespace mesh_sampling -}; // namespace mesh_sampling \ No newline at end of file +#include "mesh_sampling.hpp" \ No newline at end of file diff --git a/include/mesh_sampling/mesh_sampling.hpp b/include/mesh_sampling/mesh_sampling.hpp new file mode 100644 index 0000000..27d1be7 --- /dev/null +++ b/include/mesh_sampling/mesh_sampling.hpp @@ -0,0 +1,253 @@ + +#pragma once +#include "mesh_sampling.h" +#include +#include +#include +#include + +namespace mesh_sampling { +template +void MeshSampling::create_convex(const pcl::PointCloud & cloud, const fs::path & out_path) +{ + if(cloud.empty()) + { + cerr << "Error: Empty point cloud." << endl; + return; + } + + std::ofstream out(out_path); + if(!out.is_open()) + { + std::cerr << "Error: Could not open file: " << out_path << std::endl; + return; + } + + // Convert PCL cloud to a flat array for Qhull input + std::vector qhull_input; + for(const auto & pt : cloud) + { + qhull_input.push_back(pt.x); + qhull_input.push_back(pt.y); + qhull_input.push_back(pt.z); + } + + // Create Qhull object + Qhull qhull; + try + { + qhull.runQhull("pcl_input", 3, cloud.size(), qhull_input.data(), "Qt"); // 3D, triangulate option + } + catch(const std::exception & e) + { + cerr << "Qhull exception: " << e.what() << endl; + return; + } + + int dim = qhull.hullDimension(); + int numfacets = qhull.facetList().count(); + int totneighbors = numfacets * dim; // not accurate for non-simplicial facets + out << dim << "\n" << qhull.points().size() << " " << numfacets << " " << totneighbors / 2 << "\n"; + + std::vector> points; + for(QhullPoints::ConstIterator i = qhull.points().begin(); i != qhull.points().end(); ++i) + { + QhullPoint point = *i; + points.push_back(point.toStdVector()); + } + + for(const auto & point : points) + { + for(size_t i = 0; i < point.size(); ++i) + { + out << std::setw(6) << point[i] << (i < point.size() - 1 ? " " : "\n"); + } + } + + QhullFacetList facets = qhull.facetList(); + std::vector> facetVertices; + + QhullFacetListIterator j(facets); + while(j.hasNext()) + { + QhullFacet f = j.next(); + std::vector vertices; + if(!f.isGood()) + { + continue; + } + else if(!f.isTopOrient() && f.isSimplicial()) + { + QhullVertexSet vs = f.vertices(); + vertices.push_back(vs[1].point().id()); + vertices.push_back(vs[0].point().id()); + for(int i = 2; i < static_cast(vs.size()); ++i) + { + vertices.push_back(vs[i].point().id()); + } + } + else + { + QhullVertexSetIterator k(f.vertices()); + while(k.hasNext()) + { + QhullVertex vertex = k.next(); + vertices.push_back(vertex.point().id()); + } + } + facetVertices.push_back(vertices); + } + + for(const auto & vertices : facetVertices) + { + out << vertices.size() << " "; + for(int v : vertices) + { + out << v << " "; + } + out << "\n"; + } + + out.close(); + std::cout << "Convex file saved to " << out_path << std::endl; +} + +template +void MeshSampling::create_convexes(const std::map> &clouds, const fs::path &out_path){ + if(!fs::is_directory(out_path)){ + std::cerr << "[Error] create_convexes, out_path has to be a directory" << std::endl; + return; + } + for(const auto & cloud : clouds){ + create_convex(cloud.second, out_path / (fs::path(cloud.first).filename().stem().string() + "-ch.txt")); + } +} + +template +std::map> MeshSampling::create_clouds(unsigned N, const fs::path & out_path, const std::string & extension, bool binary_mode) +{ + std::map> clouds; + if(!out_path.empty()){ + if(!fs::is_directory(out_path) && meshes_.size() > 1) + { + std::cerr << "[Error] create_clouds, out_path is not a directory" << std::endl;; + return {}; + } + + if(extension.empty()){ + std::cerr << "[Error] create_clouds, extension is not set" << std::endl; + return {}; + } + + for(const auto & mesh : meshes_) + { + auto path = fs::is_directory(out_path) ? out_path / (fs::path(mesh.first).filename().stem().string() + extension) : out_path; + auto cloud = create_cloud(mesh.second->scene(), N, path, binary_mode); + clouds.insert({mesh.first, cloud}); + } + } + else{ + for(const auto & mesh : meshes_) + { + auto cloud = create_cloud(mesh.second->scene(), N, {}, binary_mode); + clouds.insert({mesh.first, cloud}); + } + } + + return clouds; +} + +template +pcl::PointCloud MeshSampling::cloud(const unsigned N){ + WeightedRandomSampling sampler(meshes_.begin()->second->scene()); + auto cloud = sampler.weighted_random_sampling(N); + return *cloud; +} + +template +pcl::PointCloud MeshSampling::create_cloud(const aiScene * scene, + const unsigned N, + const fs::path & out_path, + bool binary_mode) +{ + WeightedRandomSampling sampler(scene); + auto cloud = sampler.weighted_random_sampling(N); + + if(!out_path.empty() && !fs::is_directory(out_path)) + { + auto extension = boost::algorithm::to_lower_copy(out_path.extension().string()); + bool success = true; + if(extension == ".pcd") + { + success = pcl::io::savePCDFile(out_path, *cloud, binary_mode) == 0; + } + else if(extension == ".ply") + { + success = pcl::io::savePLYFile(out_path, *cloud, binary_mode) == 0; + } + else if(extension == ".qc") + { + success = mesh_sampling::io::saveQhullFile(out_path, *cloud); + } + else if(extension == ".stl") + { + pcl::ConvexHull convex{}; + const auto shared_cloud = typename pcl::PointCloud::ConstPtr(cloud.release()); + convex.setInputCloud(shared_cloud); + pcl::PolygonMesh mesh; + convex.reconstruct(mesh); + pcl::PointCloud cloud{}; + pcl::fromPCLPointCloud2(mesh.cloud, cloud); + + // Compute the convex center + Eigen::Vector3f center = Eigen::Vector3f::Zero(); + for(auto & poly : mesh.polygons) + { + if(poly.vertices.size() != 3) + { + throw std::runtime_error("pcl::ConvexHull did not reconstruct a triangular mesh"); + } + center += (cloud[poly.vertices[0]].getVector3fMap() + cloud[poly.vertices[1]].getVector3fMap() + + cloud[poly.vertices[2]].getVector3fMap()) + / (3 * mesh.polygons.size()); + } + + // Make sure all normals point away from the mesh center + // Since the mesh is convex this is enough to ensure the normals are consistent + for(auto & poly : mesh.polygons) + { + Eigen::Vector3f p1 = cloud[poly.vertices[0]].getVector3fMap(); + Eigen::Vector3f p2 = cloud[poly.vertices[1]].getVector3fMap(); + Eigen::Vector3f p3 = cloud[poly.vertices[2]].getVector3fMap(); + Eigen::Vector3f n = (p2 - p1).cross(p3 - p1); + if(n.dot(center - p1) > 0) + { + std::swap(poly.vertices[1], poly.vertices[2]); + } + } +#if PCL_VERSION_COMPARE(>=, 1, 8, 0) + success = pcl::io::savePolygonFileSTL(out_path, mesh, binary_mode); +#else + success = pcl::io::savePolygonFileSTL(out, mesh); +#endif + } + else + { + std::cerr << "Output pointcloud type " << extension << " is not supported"; + return pcl::PointCloud(); + } + + if(!success) + { + std::cerr << "Saving to " << out_path << " failed." << std::endl; + return pcl::PointCloud(); + } + else + { + std::cout << "Saving cloud to " << out_path << " success" << std::endl; + } + } + + return *cloud; +} +} \ No newline at end of file From e81fde56a04923253db07c6016af4fdc77a5371a Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 15:41:15 +0900 Subject: [PATCH 31/83] doc(readme): explain new usages --- README.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f1ea0dd..1185a73 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,18 @@ sudo make install A simple binary executable `mesh_sampling` is provided. It'll convert any model supported by ASSIMP into its corresponding pointcloud with a given number of points. The command line is of the general form: +* From file to file + +``` +mesh_sampling /path/to/model. --out /path/to/cloud/cloud. --type xyz_rgb_normal --samples 10000 --binary +``` + +* From folder to folder + +Here the default cloud format is `.qc`. For all files in `/path/to/models` a file `/path/to/cloud/filename.qc` will be generated. + ``` -mesh_sampling /path/to/model. /path/to/cloud/cloud. --type xyz_rgb_normal --samples 10000 --binary +mesh_sampling /path/to/models --out /path/to/cloud --type xyz_rgb_normal --samples 10000 --binary ``` Where: @@ -94,9 +104,17 @@ pcl_viewer /tmp/cloud.pcd -normals_scale 5 -normals 1 ### Generating convex files using qhull -To generate convex files, you will first need to convex your mesh to qhull's `.qc` format, then use `qconvex` to generate the convex hull. +To generate convex files, you need to add `--convex` option to the command line. The convex file will be generated in the specified folder as `filename-ch.txt`. + +* From file +``` +mesh_sampling /path/to/model. --out /tmp/test.qc --convex /path --type xyz --samples 10000 ``` -mesh_sampling /path/to/model. /tmp/test.qc --type xyz --samples 10000 -qconvex TI /tmp/test.qc TO /tmp/test-ch.txt Qt o f + +* From folder (check all files with respect to the supported extensions ".ply", ".pcd", ".qc", ".stl") ``` +mesh_sampling /path/to/models --out /tmp --convex /tmp --type xyz --samples 10000 +``` + +> Cloud saving is optional, you can remove `--out` option to avoid it. \ No newline at end of file From 1799b374be564da03f896f692e5a6dfd5b7b98b5 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 15:41:57 +0900 Subject: [PATCH 32/83] feat(mesh_sampling): consider edges cases --- src/mesh_sampling.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/mesh_sampling.cpp b/src/mesh_sampling.cpp index 17cd715..3feb35e 100644 --- a/src/mesh_sampling.cpp +++ b/src/mesh_sampling.cpp @@ -14,8 +14,9 @@ void MeshSampling::load(const fs::path & in_path, float scale) if(fs::is_directory(in_path)) { for(const auto & dir_entry : std::filesystem::directory_iterator{in_path}) - meshes_.insert( - std::make_pair(dir_entry.path(), std::make_shared(dir_entry.path().string(), scale))); + if(check_supported(dir_entry.path().extension(), supported_extensions)) + meshes_.insert( + std::make_pair(dir_entry.path(), std::make_shared(dir_entry.path().string(), scale))); } else { @@ -29,13 +30,10 @@ void MeshSampling::load(const fs::path & in_path, float scale) } } -bool MeshSampling::check_supported(const std::vector & supported, const std::string & value) +bool MeshSampling::check_supported(const std::string & type, const std::vector & supported) { - if(std::find(supported.begin(), supported.end(), value) == supported.end()) + if(std::find(supported.begin(), supported.end(), type) == supported.end()) { - std::cerr << "This program only supports: "; - std::copy(supported.begin(), supported.end(), std::ostream_iterator(std::cerr, ", ")); - std::cerr << std::endl; return false; } From 2cca1f8977e87059a184d8e111245a1e102fb1ec Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 15:42:41 +0900 Subject: [PATCH 33/83] feat(utils): consider folder as input and export convexes --- utils/mesh_sampling.cpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/utils/mesh_sampling.cpp b/utils/mesh_sampling.cpp index ef062d3..def5274 100644 --- a/utils/mesh_sampling.cpp +++ b/utils/mesh_sampling.cpp @@ -29,7 +29,8 @@ int main(int argc, char ** argv) desc.add_options() ("help", "Produce this message") ("in", po::value(), "Input mesh (supported by ASSIMP)") - ("out", po::value(), "Output file (ply, pcd, qc, stl)") + ("out", po::value()->default_value({}), "Output file (ply, pcd, qc, stl)") + ("convex", po::value()->default_value({}), "OUtput convex directory") ("samples", po::value()->default_value(10000), "Number of points to sample") ("scale", po::value()->default_value(1.0), "Scale factor applied to the mesh") ("type", po::value()->default_value("xyz_rgb_normal"), "Type of cloud to generate (xyz, xyz_rgb, xyz_rgb_normal)") @@ -39,7 +40,7 @@ int main(int argc, char ** argv) po::positional_options_description pos; pos.add("in", 1); - pos.add("out", 1); + // pos.add("out", 1); // parse arguments and save them in the variable map (vm) po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm); @@ -56,16 +57,21 @@ int main(int argc, char ** argv) std::string in_path = vm["in"].as(); std::string out_path = vm["out"].as(); + fs::path convex_path = vm["convex"].as(); fs::path in_p(in_path); fs::path out_p(out_path); const auto out_extension = boost::algorithm::to_lower_copy(out_p.extension().string()); const unsigned N = vm["samples"].as(); const std::string cloud_type = vm["type"].as(); const bool binary_format = vm["binary"].as(); - + MeshSampling mesh_sampler(in_p); - mesh_sampler.check_supported(supported_cloud_type, cloud_type); + if(!mesh_sampler.check_supported(cloud_type)){\ + std::cerr << "Type not suppported : "; + std::copy(mesh_sampling::supported_cloud_type.begin(), mesh_sampling::supported_cloud_type.end(), std::ostream_iterator(std::cerr, ", ")); + std::cerr << std::endl; + } if(convert) { @@ -75,20 +81,27 @@ int main(int argc, char ** argv) { if(cloud_type == "xyz") { - auto mesh = mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); - mesh_sampler.create_convex(mesh, "/tmp/test-ch.txt"); + auto mesh = mesh_sampler.create_clouds(N, out_p, ".qc", binary_format); + if(!convex_path.empty()) + mesh_sampler.create_convexes(mesh, convex_path); } else if(cloud_type == "xyz_rgb") { - mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); + auto mesh = mesh_sampler.create_clouds(N, out_p, ".qc", binary_format); + if(!convex_path.empty()) + mesh_sampler.create_convexes(mesh, convex_path); } else if(cloud_type == "xyz_normal") { - mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); + auto mesh = mesh_sampler.create_clouds(N, out_p, ".qc", binary_format); + if(!convex_path.empty()) + mesh_sampler.create_convexes(mesh, convex_path); } else if(cloud_type == "xyz_rgb_normal") { - mesh_sampler.create_cloud(mesh_sampler.mesh(in_p)->scene(), N, out_p, binary_format); + auto mesh = mesh_sampler.create_clouds(N, out_p, ".qc", binary_format); + if(!convex_path.empty()) + mesh_sampler.create_convexes(mesh, convex_path); } } From b96ce27cbb7c413c32ae782066e0d615827d5ca3 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 15:51:42 +0900 Subject: [PATCH 34/83] fix(ci): remove ubuntu 20.04 --- .clang-format-common.sh | 6 +++--- .github/workflows/build.yml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.clang-format-common.sh b/.clang-format-common.sh index bdc8bac..73e139b 100644 --- a/.clang-format-common.sh +++ b/.clang-format-common.sh @@ -1,12 +1,12 @@ # This script is meant to be sourced from other scripts # Check for clang-format, prefer 10 if available -if [[ -x "$(command -v clang-format-10)" ]]; then - clang_format=clang-format-10 +if [[ -x "$(command -v clang-format-14)" ]]; then + clang_format=clang-format-14 elif [[ -x "$(command -v clang-format)" ]]; then clang_format=clang-format else - echo "clang-format or clang-format-10 must be installed" + echo "clang-format or clang-format-14 must be installed" exit 1 fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fd2ec2..11809af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,13 +10,13 @@ on: jobs: clang-format: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install clang-format run: | sudo apt-get -qq update - sudo apt-get -qq install clang-format-10 + sudo apt-get -qq install clang-format-14 - name: Run clang-format-check run: | ./.clang-format-check.sh @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, ubuntu-22.04] + os: [ubuntu-22.04, ubuntu-24.04] build-type: [Debug, RelWithDebInfo] compiler: [gcc, clang] From 5033a3d6796c040dace000e1bb5f7b9e9b26a0e0 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 16:03:03 +0900 Subject: [PATCH 35/83] fix(test): apply latest changes --- tests/test_mesh_sampling/src/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_mesh_sampling/src/main.cpp b/tests/test_mesh_sampling/src/main.cpp index fcc3913..eb23a92 100644 --- a/tests/test_mesh_sampling/src/main.cpp +++ b/tests/test_mesh_sampling/src/main.cpp @@ -57,20 +57,20 @@ int main(int argc, char ** argv) MeshSampling sampler(model_path); std::cout << "Sampling " << N << " points from " << model_path << std::endl; - WeightedRandomSampling sampling_xyz(sampler.mesh().scene()); + WeightedRandomSampling sampling_xyz(sampler.mesh(model_path)->scene()); auto cloud_xyz = sampling_xyz.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_xyz.pcd", *cloud_xyz); std::cout << "Cloud size: " << cloud_xyz->size() << ", expected: " << N << std::endl; - WeightedRandomSampling sampling_rgb(sampler.mesh().scene()); + WeightedRandomSampling sampling_rgb(sampler.mesh(model_path)->scene()); auto cloud_rgb = sampling_rgb.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_rgb.pcd", *cloud_rgb); - WeightedRandomSampling sampling_normal(sampler.mesh().scene()); + WeightedRandomSampling sampling_normal(sampler.mesh(model_path)->scene()); auto cloud_normal = sampling_normal.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_normal.pcd", *cloud_normal); - WeightedRandomSampling sampling_rgb_normal(sampler.mesh().scene()); + WeightedRandomSampling sampling_rgb_normal(sampler.mesh(model_path)->scene()); auto cloud_rgb_normal = sampling_rgb_normal.weighted_random_sampling(N); pcl::io::savePCDFileASCII("/tmp/example_rgb_normal.pcd", *cloud_rgb_normal); } From b7a3b437ed4366b2c118e799dc6fb655980a677b Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Wed, 4 Jun 2025 16:13:04 +0900 Subject: [PATCH 36/83] fix(ci): remove package for bionic and focal, add noble --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index e8799a9..5363b16 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -20,7 +20,7 @@ jobs: update-stable-and-head: true matrix: | { - "dist": ["bionic", "focal", "jammy"], + "dist": ["jammy", "noble"], "arch": ["amd64"] } secrets: From ce4845bf9e9c0d89fb002163d6816c16b10ed2f6 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Fri, 6 Jun 2025 19:14:38 +0900 Subject: [PATCH 37/83] feat(cli11): remove boost dependency, use cli11 as option handler --- CMakeLists.txt | 1 - README.md | 10 +- include/mesh_sampling/CLI11.hpp | 11527 ++++++++++++++++++++++++++++++ utils/CMakeLists.txt | 2 +- utils/mesh_sampling.cpp | 118 +- 5 files changed, 11579 insertions(+), 79 deletions(-) create mode 100644 include/mesh_sampling/CLI11.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b20aef..09bcbba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,6 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules) # For now, manually add those to the mesh_samplingConfig.cmake script find_package(mesh_sampling_3rd_party_assimp REQUIRED) find_package(mesh_sampling_3rd_party_PCL REQUIRED) -add_project_dependency(Boost COMPONENTS program_options REQUIRED) find_package(Qhull REQUIRED) diff --git a/README.md b/README.md index 1185a73..ef27f37 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ See `mesh_sampling --help` for more options. Example: ```bash -mesh_sampling /path/to/model.dae /tmp/cloud.pcd --type xyz_rgb_normal --samples 10000 --binary +mesh_sampling --in /path/to/model.dae --out /tmp/cloud.pcd --type xyz_rgb_normal --samples 10000 --binary pcl_viewer /tmp/cloud.pcd -normals_scale 5 -normals 1 ``` @@ -108,13 +108,13 @@ To generate convex files, you need to add `--convex` option to the command line. * From file -``` -mesh_sampling /path/to/model. --out /tmp/test.qc --convex /path --type xyz --samples 10000 +```bash +mesh_sampling --in /path/to/model. --out /tmp/test.qc --convex /path --type xyz --samples 10000 ``` * From folder (check all files with respect to the supported extensions ".ply", ".pcd", ".qc", ".stl") -``` -mesh_sampling /path/to/models --out /tmp --convex /tmp --type xyz --samples 10000 +```bash +mesh_sampling --in /path/to/models --out /tmp --convex /tmp --type xyz --samples 10000 ``` > Cloud saving is optional, you can remove `--out` option to avoid it. \ No newline at end of file diff --git a/include/mesh_sampling/CLI11.hpp b/include/mesh_sampling/CLI11.hpp new file mode 100644 index 0000000..9fa9cc0 --- /dev/null +++ b/include/mesh_sampling/CLI11.hpp @@ -0,0 +1,11527 @@ +// CLI11: Version 2.5.0 +// 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.5.0 +// +// CLI11 2.5.0 Copyright (c) 2017-2025 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 5 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "2.5.0" + + + + +// 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 + +/** 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 + + + +#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 + 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 +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) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// 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; +} + +/// 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); + +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 = '\''); + +/// 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('\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 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); +} + +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static 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: + case std::string::npos: + 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) { + // 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) { + 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) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + 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); + } + handle_secondary_array(str); + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + handle_secondary_array(str); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + 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 + 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(word.length() + charsWritten > paragraphWidth) { + out << '\n' << linePrefix; + charsWritten = 0; + } + + 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 { + 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::rjoin(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::rjoin(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 {}; + +/// An instance to use in EnableIf +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, + 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>::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>::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 : int { + 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(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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 and trailing spaces + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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; + } + return integral_conversion(input, output); +} + +/// 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(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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.length() == 0) { + 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 { + std::ifstream input{name}; + 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); +}; + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// 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 { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +/// 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(); +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + +/// 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"); + +/// 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 +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +/// 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) {} +}; + +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) { + 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}; +} + +// 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; +} + +} // 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::to_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 { + 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); +}; + +namespace detail { +/// 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 IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + 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{}; + }; +} + +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{}; + }; +} + +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 { + +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 +/// @} + + + + +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 { + 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 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}; + + /// @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" label + 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 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; } + + ///@} + /// @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_; } + + ///@} +}; + +/// 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; + +using Option_p = std::unique_ptr::value - && details::is_supported_number::value - // Prevent implicit type conversion from singed to unsigned types - && std::is_signed::value != std::is_unsigned::value - , C>::type get( A from = std::numeric_limits::min( ), - B to = std::numeric_limits::max( ) ) { - return get( static_cast( from ), static_cast( to ) ); - } - - /** - * \brief Generate a random character in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random character in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value - , T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) { - if (from < to) // Allow range from higher to lower - return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); - return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); - } - - /** - * \brief Generate a bool value with specific probability - * by std::bernoulli_distribution - * \param probability The probability of generating true in [0; 1] range - * 0 means always false, 1 means always true - * \return 'true' with 'probability' probability ('false' otherwise) - */ - template - static typename std::enable_if::value - , bool>::type get( const double probability = 0.5 ) { - assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range - return BoolDist{ probability }( engine_instance( ) ); - } - - /** - * \brief Return random value from initilizer_list - * \param init_list initilizer_list with values - * \return Random value from initilizer_list - * \note Should be 1 or more elements in initilizer_list - * \note Warning! Elements in initilizer_list can't be moved: - * https://stackoverflow.com/a/8193157/5734836 - */ - template - static T get( std::initializer_list init_list ) { - assert( 0u != init_list.size( ) ); - return *get( init_list.begin( ), init_list.end( ) ); - } - - /** - * \brief Return random iterator from iterator range - * \param first, last - the range of elements - * \return Random iterator from [first, last) range - * \note If first == last, return last - */ - template - static typename std::enable_if::value - , InputIt>::type get( InputIt first, InputIt last ) { - const auto size = std::distance( first, last ); - if( 0 == size ) return last; - using diff_t = typename std::iterator_traits::difference_type; - return std::next( first, get( 0, size - 1 ) ); - } - - /** - * \brief Return random iterator from Container - * \param container The container with elements - * \return Random iterator from container - * \note If container is empty return std::end( container ) iterator - */ - template - static auto get( Container& container ) -> - typename std::enable_if::value - , decltype(std::begin(container)) - >::type { - return get( std::begin( container ), std::end( container ) ); - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, typename A> - static typename std::enable_if< - details::has_reserve>::value - , Container>::type get(A from, A to, std::size_t size) { - Container container; - - container.reserve(size); - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(from, to)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template< - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if< - std::is_same::value - && details::has_reserve>::value - , Container>::type get(A start, B end, std::size_t size) { - Container container; - - container.reserve(size); - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, typename A> - static typename std::enable_if< - !details::has_reserve>::value - , Container>::type get(A start, A end, std::size_t size) { - Container container; - - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template< - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if< - std::is_same::value - && !details::has_reserve>::value - , Container>::type get(A start, B end, std::size_t size) { - Container container; - - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return array-like container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param N The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, std::size_t N, typename A> - static typename std::enable_if< - !details::has_insert>::value - , Container>::type get(A start, A end) { - Container container = {{ 0 }}; - - for (std::size_t i = 0; i < N; ++i) - container[i] = get(start, end); - - return container; - } - - /** - * \brief Return array-like container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template< - template class Container, - std::size_t N, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if< - std::is_same::value - && !details::has_insert>::value - , Container>::type get(A start, B end) { - Container container = {{ 0 }}; - - for (std::size_t i = 0; i < N; ++i) - container[i] = get(start, end); - - return container; - } - - /** - * \brief Return random pointer from built-in array - * \param array The built-in array with elements - * \return Pointer to random element in array - */ - template - static T* get( T( &array )[ N ] ) { - return std::addressof( array[ get( 0, N - 1 ) ] ); - } - - /** - * \brief Return value from custom Dist distribution - * seeded by internal random engine - * \param Dist The type of custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom distribution - */ - template - static typename Dist::result_type get( Args&&... args ) { - return Dist{ std::forward( args )... }( engine_instance( ) ); - } - - /** - * \brief Return value from custom 'dist' distribution - * seeded by internal random engine - * \param dist The custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom 'dist' distribution - */ - template - static typename Dist::result_type get( Dist& dist ) { - return dist( engine_instance( ) ); - } - - /** - * \brief Reorders the elements in the given range [first, last) - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param first, last - the range of elements to shuffle randomly - */ - template - static void shuffle( RandomIt first, RandomIt last ) { - std::shuffle( first, last, engine_instance( ) ); - } - - /** - * \brief Reorders the elements in the given container - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param container - the container with elements to shuffle randomly - */ - template - static void shuffle( Container& container ) { - shuffle( std::begin( container ), std::end( container ) ); - } - - /// return internal engine by copy - static Engine get_engine( ) { - return engine_instance( ); - } - - /// return internal engine by ref - static Engine& engine() { - return engine_instance(); - } - protected: - /// get reference to the static engine instance - static Engine& engine_instance( ) { - static Engine engine{ Seeder{ }( ) }; - return engine; - } - }; - - /** - * \brief Base template class for random - * with thread_local API and thread_local internal member storage - * \note it IS thread safe but less efficient then - * basic_random_static - * \param Engine A random engine with interface like in the std::mt19937 - * \param Seeder A seeder type which return seed for internal engine - * through operator() - */ - template< - typename Engine, - typename Seeder = seeder_default, - template class IntegerDist = std::uniform_int_distribution, - template class RealDist = std::uniform_real_distribution, - typename BoolDist = std::bernoulli_distribution - > - class basic_random_thread_local { - public: - basic_random_thread_local( ) = delete; - - /// Type of used random number engine - using engine_type = Engine; - - /// Type of used random number seeder - using seeder_type = Seeder; - - /// Type of used integer distribution - template - using integer_dist_t = IntegerDist; - - /// Type of used real distribution - template - using real_dist_t = RealDist; - - /// Type of used bool distribution - using bool_dist_t = BoolDist; - - /// Key type for getting common type numbers or objects - using common = details::common; - - /** - * \return The minimum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type min( ) { - return Engine::min( ); - } - - /** - * \return The maximum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type max( ) { - return Engine::max( ); - } - - /// Advances the internal state by z times - static void discard( const unsigned long long z ) { - engine_instance( ).discard( z ); - } - - /// Reseed by Seeder - static void reseed( ) { - Seeder seeder; - seed( seeder( ) ); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param value The seed value to use - * in the initialization of the internal state - */ - static void seed( const typename Engine::result_type value = - Engine::default_seed ) { - engine_instance( ).seed( value ); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param seq The seed sequence - * to use in the initialization of the internal state - */ - template - static void seed( Sseq& seq ) { - engine_instance( ).seed( seq ); - } - - /// return random number from engine in [min(), max()] range - static typename Engine::result_type get( ) { - return engine_instance( )( ); - } - - /** - * \brief Compares internal pseudo-random number engine - * with 'other' pseudo-random number engine. - * Two engines are equal, if their internal states - * are equivalent, that is, if they would generate - * equivalent values for any number of calls of operator() - * \param other The engine, with which the internal engine will be compared - * \return true, if other and internal engine are equal - */ - static bool is_equal( const Engine& other ) { - return engine_instance( ) == other; - } - - /** - * \brief Serializes the internal state of the - * internal pseudo-random number engine as a sequence - * of decimal numbers separated by one or more spaces, - * and inserts it to the stream ost. The fill character - * and the formatting flags of the stream are - * ignored and unaffected. - * \param ost The output stream to insert the data to - */ - template - static void serialize( std::basic_ostream& ost ) { - ost << engine_instance( ); - } - - /** - * \brief Restores the internal state of the - * internal pseudo-random number engine from - * the serialized representation, which - * was created by an earlier call to 'serialize' - * using a stream with the same imbued locale and - * the same CharT and Traits. - * If the input cannot be deserialized, - * internal engine is left unchanged and failbit is raised on ist - * \param ost The input stream to extract the data from - */ - template - static void deserialize( std::basic_istream& ist ) { - ist >> engine_instance( ); - } - - /** - * \brief Generate a random integer number in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random integer number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value - , T>::type get( T from = std::numeric_limits::min( ), - T to = std::numeric_limits::max( ) ) { - if( from < to ) // Allow range from higher to lower - return IntegerDist{ from, to }( engine_instance( ) ); - return IntegerDist{ to, from }( engine_instance( ) ); - } - - /** - * \brief Generate a random real number in a [from; to] range - * by std::uniform_real_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random real number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value - , T>::type get( T from = std::numeric_limits::min( ), - T to = std::numeric_limits::max( ) ) { - if( from < to ) // Allow range from higher to lower - return RealDist{ from, to }( engine_instance( ) ); - return RealDist{ to, from }( engine_instance( ) ); - } - - /** - * \brief Generate a random byte number in a [from; to] range - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random byte number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value - , T>::type get( T from = std::numeric_limits::min( ), - T to = std::numeric_limits::max( ) ) { - // Choose between short and unsigned short for byte conversion - using short_t = typename std::conditional::value, - short, unsigned short>::type; - - return static_cast( get( from, to ) ); - } - - /** - * \brief Generate a random common_type number in a [from; to] range - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random common_type number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Allow implicit type conversion - * \note Prevent implicit type conversion from singed to unsigned types - * Why? std::common_type chooses unsigned value, - * then Signed value will be converted to Unsigned value - * which gives us a wrong range for random values. - * https://stackoverflow.com/a/5416498/5734836 - */ - template< - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type - > - static typename std::enable_if< - std::is_same::value - && details::is_supported_number::value - && details::is_supported_number::value - // Prevent implicit type conversion from singed to unsigned types - && std::is_signed::value != std::is_unsigned::value - , C>::type get( A from = std::numeric_limits::min( ), - B to = std::numeric_limits::max( ) ) { - return get( static_cast( from ), static_cast( to ) ); - } - - /** - * \brief Generate a random character in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random character in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value - , T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) { - if (from < to) // Allow range from higher to lower - return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); - return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); - } - - /** - * \brief Generate a bool value with specific probability - * by std::bernoulli_distribution - * \param probability The probability of generating true in [0; 1] range - * 0 means always false, 1 means always true - * \return 'true' with 'probability' probability ('false' otherwise) - */ - template - static typename std::enable_if::value - , bool>::type get( const double probability = 0.5 ) { - assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range - return BoolDist{ probability }( engine_instance( ) ); - } - - /** - * \brief Return random value from initilizer_list - * \param init_list initilizer_list with values - * \return Random value from initilizer_list - * \note Should be 1 or more elements in initilizer_list - * \note Warning! Elements in initilizer_list can't be moved: - * https://stackoverflow.com/a/8193157/5734836 - */ - template - static T get( std::initializer_list init_list ) { - assert( 0u != init_list.size( ) ); - return *get( init_list.begin( ), init_list.end( ) ); - } - - /** - * \brief Return random iterator from iterator range - * \param first, last - the range of elements - * \return Random iterator from [first, last) range - * \note If first == last, return last - */ - template - static typename std::enable_if::value - , InputIt>::type get( InputIt first, InputIt last ) { - const auto size = std::distance( first, last ); - if( 0 == size ) return last; - using diff_t = typename std::iterator_traits::difference_type; - return std::next( first, get( 0, size - 1 ) ); - } - - /** - * \brief Return random iterator from Container - * \param container The container with elements - * \return Random iterator from container - * \note If container is empty return std::end( container ) iterator - */ - template - static auto get( Container& container ) -> - typename std::enable_if::value - , decltype(std::begin(container)) - >::type { - return get( std::begin( container ), std::end( container ) ); - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, typename A> - static typename std::enable_if< - details::has_reserve>::value - , Container>::type get(A from, A to, std::size_t size) { - Container container; - - container.reserve(size); - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(from, to)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template< - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if< - std::is_same::value - && details::has_reserve>::value - , Container>::type get(A start, B end, std::size_t size) { - Container container; - - container.reserve(size); - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, typename A> - static typename std::enable_if< - !details::has_reserve>::value - , Container>::type get(A start, A end, std::size_t size) { - Container container; - - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template< - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if< - std::is_same::value - && !details::has_reserve>::value - , Container>::type get(A start, B end, std::size_t size) { - Container container; - - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return array-like container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param N The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, std::size_t N, typename A> - static typename std::enable_if< - !details::has_insert>::value - , Container>::type get(A start, A end) { - Container container = {{ 0 }}; - - for (std::size_t i = 0; i < N; ++i) - container[i] = get(start, end); - - return container; - } - - /** - * \brief Return array-like container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template< - template class Container, - std::size_t N, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if< - std::is_same::value - && !details::has_insert>::value - , Container>::type get(A start, B end) { - Container container = {{ 0 }}; - - for (std::size_t i = 0; i < N; ++i) - container[i] = get(start, end); - - return container; - } - - /** - * \brief Return random pointer from built-in array - * \param array The built-in array with elements - * \return Pointer to random element in array - */ - template - static T* get( T( &array )[ N ] ) { - return std::addressof( array[ get( 0, N - 1 ) ] ); - } - - /** - * \brief Return value from custom Dist distribution - * seeded by internal random engine - * \param Dist The type of custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom distribution - */ - template - static typename Dist::result_type get( Args&&... args ) { - return Dist{ std::forward( args )... }( engine_instance( ) ); - } - - /** - * \brief Return value from custom 'dist' distribution - * seeded by internal random engine - * \param dist The custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom 'dist' distribution - */ - template - static typename Dist::result_type get( Dist& dist ) { - return dist( engine_instance( ) ); - } - - /** - * \brief Reorders the elements in the given range [first, last) - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param first, last - the range of elements to shuffle randomly - */ - template - static void shuffle( RandomIt first, RandomIt last ) { - std::shuffle( first, last, engine_instance( ) ); - } - - /** - * \brief Reorders the elements in the given container - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param container - the container with elements to shuffle randomly - */ - template - static void shuffle( Container& container ) { - shuffle( std::begin( container ), std::end( container ) ); - } - - /// return internal engine by copy - static Engine get_engine( ) { - return engine_instance( ); - } - - /// return internal engine by ref - static Engine& engine() { - return engine_instance(); - } - protected: - /// get reference to the thread local engine instance - static Engine& engine_instance( ) { - thread_local Engine engine{ Seeder{ }( ) }; - return engine; - } - }; - - /** - * \brief Base template class for random - * with local API and local internal member storage - * \note it IS thread safe but less efficient then - * basic_random_static - * \param Engine A random engine with interface like in the std::mt19937 - * \param Seeder A seeder type which return seed for internal engine - * through operator() - */ - template< - typename Engine, - typename Seeder = seeder_default, - template class IntegerDist = std::uniform_int_distribution, - template class RealDist = std::uniform_real_distribution, - typename BoolDist = std::bernoulli_distribution - > - class basic_random_local { - public: - /// Type of used random number engine - using engine_type = Engine; - - /// Type of used random number seeder - using seeder_type = Seeder; - - /// Type of used integer distribution - template - using integer_dist_t = IntegerDist; - - /// Type of used real distribution - template - using real_dist_t = RealDist; - - /// Type of used bool distribution - using bool_dist_t = BoolDist; - - /// Key type for getting common type numbers or objects - using common = details::common; - - /** - * \return The minimum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type min( ) { - return Engine::min( ); - } - - /** - * \return The maximum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type max( ) { - return Engine::max( ); - } - - /// Advances the internal state by z times - void discard( const unsigned long long z ) { - m_engine.discard( z ); - } - - /// Reseed by Seeder - void reseed( ) { - Seeder seeder; - seed( seeder( ) ); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param value The seed value to use - * in the initialization of the internal state - */ - void seed( const typename Engine::result_type value = - Engine::default_seed ) { - m_engine.seed( value ); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param seq The seed sequence - * to use in the initialization of the internal state - */ - template - void seed( Sseq& seq ) { - m_engine.seed( seq ); - } - - /// return random number from engine in [min(), max()] range - typename Engine::result_type get( ) { - return m_engine( ); - } - - /** - * \brief Compares internal pseudo-random number engine - * with 'other' pseudo-random number engine. - * Two engines are equal, if their internal states - * are equivalent, that is, if they would generate - * equivalent values for any number of calls of operator() - * \param other The engine, with which the internal engine will be compared - * \return true, if other and internal engine are equal - */ - bool is_equal( const Engine& other ) { - return m_engine == other; - } - - /** - * \brief Serializes the internal state of the - * internal pseudo-random number engine as a sequence - * of decimal numbers separated by one or more spaces, - * and inserts it to the stream ost. The fill character - * and the formatting flags of the stream are - * ignored and unaffected. - * \param ost The output stream to insert the data to - */ - template - void serialize( std::basic_ostream& ost ) { - ost << m_engine; - } - - /** - * \brief Restores the internal state of the - * internal pseudo-random number engine from - * the serialized representation, which - * was created by an earlier call to 'serialize' - * using a stream with the same imbued locale and - * the same CharT and Traits. - * If the input cannot be deserialized, - * internal engine is left unchanged and failbit is raised on ist - * \param ost The input stream to extract the data from - */ - template - void deserialize( std::basic_istream& ist ) { - ist >> m_engine; - } - - /** - * \brief Generate a random integer number in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random integer number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value - , T>::type get( T from = std::numeric_limits::min( ), - T to = std::numeric_limits::max( ) ) { - if( from < to ) // Allow range from higher to lower - return IntegerDist{ from, to }( m_engine ); - return IntegerDist{ to, from }( m_engine ); - } - - /** - * \brief Generate a random real number in a [from; to] range - * by std::uniform_real_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random real number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value - , T>::type get( T from = std::numeric_limits::min( ), - T to = std::numeric_limits::max( ) ) { - if( from < to ) // Allow range from higher to lower - return RealDist{ from, to }( m_engine ); - return RealDist{ to, from }( m_engine ); - } - - /** - * \brief Generate a random byte number in a [from; to] range - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random byte number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value - , T>::type get( T from = std::numeric_limits::min( ), - T to = std::numeric_limits::max( ) ) { - // Choose between short and unsigned short for byte conversion - using short_t = typename std::conditional::value, - short, unsigned short>::type; - - return static_cast( get( from, to ) ); - } - - /** - * \brief Generate a random common_type number in a [from; to] range - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random common_type number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Allow implicit type conversion - * \note Prevent implicit type conversion from singed to unsigned types - * Why? std::common_type chooses unsigned value, - * then Signed value will be converted to Unsigned value - * which gives us a wrong range for random values. - * https://stackoverflow.com/a/5416498/5734836 - */ - template< - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type - > - typename std::enable_if< - std::is_same::value - && details::is_supported_number::value - && details::is_supported_number::value - // Prevent implicit type conversion from singed to unsigned types - && std::is_signed::value != std::is_unsigned::value - , C>::type get( A from = std::numeric_limits::min( ), - B to = std::numeric_limits::max( ) ) { - return get( static_cast( from ), static_cast( to ) ); - } - - /** - * \brief Generate a random character in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random character in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value - , T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) { - if (from < to) // Allow range from higher to lower - return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(m_engine)); - return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(m_engine)); - } - - /** - * \brief Generate a bool value with specific probability - * by std::bernoulli_distribution - * \param probability The probability of generating true in [0; 1] range - * 0 means always false, 1 means always true - * \return 'true' with 'probability' probability ('false' otherwise) - */ - template - typename std::enable_if::value - , bool>::type get( const double probability = 0.5 ) { - assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range - return BoolDist{ probability }( m_engine ); - } - - /** - * \brief Return random value from initilizer_list - * \param init_list initilizer_list with values - * \return Random value from initilizer_list - * \note Should be 1 or more elements in initilizer_list - * \note Warning! Elements in initilizer_list can't be moved: - * https://stackoverflow.com/a/8193157/5734836 - */ - template - T get( std::initializer_list init_list ) { - assert( 0u != init_list.size( ) ); - return *get( init_list.begin( ), init_list.end( ) ); - } - - /** - * \brief Return random iterator from iterator range - * \param first, last - the range of elements - * \return Random iterator from [first, last) range - * \note If first == last, return last - */ - template - typename std::enable_if::value - , InputIt>::type get( InputIt first, InputIt last ) { - const auto size = std::distance( first, last ); - if( 0 == size ) return last; - using diff_t = typename std::iterator_traits::difference_type; - return std::next( first, get( 0, size - 1 ) ); - } - - /** - * \brief Return random iterator from Container - * \param container The container with elements - * \return Random iterator from container - * \note If container is empty return std::end( container ) iterator - */ - template - auto get( Container& container ) -> - typename std::enable_if::value - , decltype(std::begin(container)) - >::type { - return get( std::begin( container ), std::end( container ) ); - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, typename A> - typename std::enable_if< - details::has_reserve>::value - , Container>::type get(A from, A to, std::size_t size) { - Container container; - - container.reserve(size); - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(from, to)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template< - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - typename std::enable_if< - std::is_same::value - && details::has_reserve>::value - , Container>::type get(A start, B end, std::size_t size) { - Container container; - - container.reserve(size); - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, typename A> - typename std::enable_if< - !details::has_reserve>::value - , Container>::type get(A start, A end, std::size_t size) { - Container container; - - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template< - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - typename std::enable_if< - std::is_same::value - && !details::has_reserve>::value - , Container>::type get(A start, B end, std::size_t size) { - Container container; - - for (std::size_t i = 0; i < size; ++i) - container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return array-like container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param N The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, std::size_t N, typename A> - typename std::enable_if< - !details::has_insert>::value - , Container>::type get(A start, A end) { - Container container = {{ 0 }}; - - for (std::size_t i = 0; i < N; ++i) - container[i] = get(start, end); - - return container; - } - - /** - * \brief Return array-like container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template< - template class Container, - std::size_t N, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - typename std::enable_if< - std::is_same::value - && !details::has_insert>::value - , Container>::type get(A start, B end) { - Container container = {{ 0 }}; - - for (std::size_t i = 0; i < N; ++i) - container[i] = get(start, end); - - return container; - } - - /** - * \brief Return random pointer from built-in array - * \param array The built-in array with elements - * \return Pointer to random element in array - */ - template - T* get( T( &array )[ N ] ) { - return std::addressof( array[ get( 0, N - 1 ) ] ); - } - - /** - * \brief Return value from custom Dist distribution - * seeded by internal random engine - * \param Dist The type of custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom distribution - */ - template - typename Dist::result_type get( Args&&... args ) { - return Dist{ std::forward( args )... }( m_engine ); - } - - /** - * \brief Return value from custom 'dist' distribution - * seeded by internal random engine - * \param dist The custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom 'dist' distribution - */ - template - typename Dist::result_type get( Dist& dist ) { - return dist( m_engine ); - } - - /** - * \brief Reorders the elements in the given range [first, last) - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param first, last - the range of elements to shuffle randomly - */ - template - void shuffle( RandomIt first, RandomIt last ) { - std::shuffle( first, last, m_engine ); - } - - /** - * \brief Reorders the elements in the given container - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param container - the container with elements to shuffle randomly - */ - template - void shuffle( Container& container ) { - shuffle( std::begin( container ), std::end( container ) ); - } - - /// return internal engine by copy - Engine get_engine( ) const { - return m_engine; - } - - /// return internal engine by ref - Engine& engine() { - return m_engine; - } - protected: - /// return engine seeded by Seeder - static Engine make_seeded_engine( ) { - // Make seeder instance for seed return by reference like std::seed_seq - return Engine{ Seeder{ }( ) }; - } - protected: - /// The random number engine - Engine m_engine{ make_seeded_engine( ) }; - }; - - /** - * \brief The basic static random alias based on a std::mt19937 - * \note It uses static methods API and data with static storage - * \note Not thread safe but more prefomance - */ - using random_static = basic_random_static; - - /** - * \brief The basic static random alias based on a std::mt19937 - * \note It uses static methods API and data with thread_local storage - * \note Thread safe but less perfomance - */ - using random_thread_local = basic_random_thread_local; - - /** - * \brief The basic static random alias based on a std::mt19937 - * \note It uses non static methods API and data with auto storage - * \note Not thread safe. Should construct on the stack at local scope - */ - using random_local = basic_random_local; +namespace effolkronium +{ + +namespace details +{ +/// Key type for getting common type numbers or objects +struct common +{ +}; + +/// True if type T is applicable by a std::uniform_int_distribution +template +struct is_uniform_int +{ + static constexpr bool value = std::is_same::value || std::is_same::value + || std::is_same::value || std::is_same::value + || std::is_same::value || std::is_same::value + || std::is_same::value || std::is_same::value; +}; + +/// True if type T is applicable by a std::uniform_real_distribution +template +struct is_uniform_real +{ + static constexpr bool value = + std::is_same::value || std::is_same::value || std::is_same::value; +}; + +/// True if type T is plain byte +template +struct is_byte +{ + static constexpr bool value = std::is_same::value || std::is_same::value; +}; + +/// True if type T is plain number type +template +struct is_supported_number +{ + static constexpr bool value = is_byte::value || is_uniform_real::value || is_uniform_int::value; +}; + +/// True if type T is character type +template +struct is_supported_character +{ + static constexpr bool value = std::is_same::value || std::is_same::value + || std::is_same::value || std::is_same::value; +}; + +/// True if type T is iterator +template +struct is_iterator +{ +private: + static char test(...); + + template::difference_type, + typename = typename std::iterator_traits::pointer, + typename = typename std::iterator_traits::reference, + typename = typename std::iterator_traits::value_type, + typename = typename std::iterator_traits::iterator_category> + static long test(U &&); + +public: + static constexpr bool value = std::is_same())), long>::value; +}; + +template +class has_reserve +{ +private: + template + static char test(...); + + template + static long test(decltype(&C::reserve)); + +public: + static constexpr bool value = std::is_same(0)), long>::value; +}; + +template +class has_insert +{ +private: + template + static char test(...); + + template + static long test(decltype(&C::insert)); + +public: + static constexpr bool value = std::is_same(0)), long>::value; +}; + +} // namespace details + +/// Default seeder for 'random' classes +struct seeder_default +{ + /// return seed sequence + std::seed_seq & operator()() + { + // MinGW issue, std::random_device returns constant value + // Use std::seed_seq with additional seed from C++ chrono + return seed_seq; + } + +private: + std::seed_seq seed_seq{{ + static_cast(std::random_device{}()), + static_cast(std::chrono::steady_clock::now().time_since_epoch().count()), + }}; +}; + +/** + * \brief Base template class for random + * with static API and static internal member storage + * \note it is NOT thread safe but more efficient then + * basic_random_thread_local + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ +template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution> +class basic_random_static +{ +public: + basic_random_static() = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min() + { + return Engine::min(); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max() + { + return Engine::max(); + } + + /// Advances the internal state by z times + static void discard(const unsigned long long z) + { + engine_instance().discard(z); + } + + /// Reseed by Seeder + static void reseed() + { + Seeder seeder; + seed(seeder()); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed(const typename Engine::result_type value = Engine::default_seed) + { + engine_instance().seed(value); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed(Sseq & seq) + { + engine_instance().seed(seq); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get() + { + return engine_instance()(); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal(const Engine & other) + { + return engine_instance() == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize(std::basic_ostream & ost) + { + ost << engine_instance(); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize(std::basic_istream & ist) + { + ist >> engine_instance(); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return IntegerDist{from, to}(engine_instance()); + return IntegerDist{to, from}(engine_instance()); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return RealDist{from, to}(engine_instance()); + return RealDist{to, from}(engine_instance()); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, short, unsigned short>::type; + + return static_cast(get(from, to)); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template::type> + static typename std::enable_if::value && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value, + C>::type + get(A from = std::numeric_limits::min(), B to = std::numeric_limits::max()) + { + return get(static_cast(from), static_cast(to)); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get( + T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return static_cast( + IntegerDist{static_cast(from), static_cast(to)}(engine_instance())); + return static_cast( + IntegerDist{static_cast(to), static_cast(from)}(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value, bool>::type get(const double probability = 0.5) + { + assert(0 <= probability && 1 >= probability); // out of [0; 1] range + return BoolDist{probability}(engine_instance()); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get(std::initializer_list init_list) + { + assert(0u != init_list.size()); + return *get(init_list.begin(), init_list.end()); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value, InputIt>::type get(InputIt first, InputIt last) + { + const auto size = std::distance(first, last); + if(0 == size) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next(first, get(0, size - 1)); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get(Container & container) -> + typename std::enable_if::value, + decltype(std::begin(container))>::type + { + return get(std::begin(container), std::end(container)); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + static typename std::enable_if>::value, Container>::type get(A from, + A to, + std::size_t size) + { + Container container; + + container.reserve(size); + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if::value && details::has_reserve>::value, + Container>::type + get(A start, B end, std::size_t size) + { + Container container; + + container.reserve(size); + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + static typename std::enable_if>::value, Container>::type get(A start, + A end, + std::size_t size) + { + Container container; + + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if::value && !details::has_reserve>::value, + Container>::type + get(A start, B end, std::size_t size) + { + Container container; + + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + static typename std::enable_if>::value, Container>::type get(A start, + A end) + { + Container container = {{0}}; + + for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if::value && !details::has_insert>::value, + Container>::type + get(A start, B end) + { + Container container = {{0}}; + + for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T * get(T (&array)[N]) + { + return std::addressof(array[get(0, N - 1)]); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get(Args &&... args) + { + return Dist{std::forward(args)...}(engine_instance()); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get(Dist & dist) + { + return dist(engine_instance()); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle(RandomIt first, RandomIt last) + { + std::shuffle(first, last, engine_instance()); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle(Container & container) + { + shuffle(std::begin(container), std::end(container)); + } + + /// return internal engine by copy + static Engine get_engine() + { + return engine_instance(); + } + + /// return internal engine by ref + static Engine & engine() + { + return engine_instance(); + } + +protected: + /// get reference to the static engine instance + static Engine & engine_instance() + { + static Engine engine{Seeder{}()}; + return engine; + } +}; + +/** + * \brief Base template class for random + * with thread_local API and thread_local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ +template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution> +class basic_random_thread_local +{ +public: + basic_random_thread_local() = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min() + { + return Engine::min(); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max() + { + return Engine::max(); + } + + /// Advances the internal state by z times + static void discard(const unsigned long long z) + { + engine_instance().discard(z); + } + + /// Reseed by Seeder + static void reseed() + { + Seeder seeder; + seed(seeder()); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed(const typename Engine::result_type value = Engine::default_seed) + { + engine_instance().seed(value); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed(Sseq & seq) + { + engine_instance().seed(seq); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get() + { + return engine_instance()(); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal(const Engine & other) + { + return engine_instance() == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize(std::basic_ostream & ost) + { + ost << engine_instance(); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize(std::basic_istream & ist) + { + ist >> engine_instance(); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return IntegerDist{from, to}(engine_instance()); + return IntegerDist{to, from}(engine_instance()); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return RealDist{from, to}(engine_instance()); + return RealDist{to, from}(engine_instance()); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, short, unsigned short>::type; + + return static_cast(get(from, to)); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template::type> + static typename std::enable_if::value && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value, + C>::type + get(A from = std::numeric_limits::min(), B to = std::numeric_limits::max()) + { + return get(static_cast(from), static_cast(to)); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value, T>::type get( + T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return static_cast( + IntegerDist{static_cast(from), static_cast(to)}(engine_instance())); + return static_cast( + IntegerDist{static_cast(to), static_cast(from)}(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value, bool>::type get(const double probability = 0.5) + { + assert(0 <= probability && 1 >= probability); // out of [0; 1] range + return BoolDist{probability}(engine_instance()); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get(std::initializer_list init_list) + { + assert(0u != init_list.size()); + return *get(init_list.begin(), init_list.end()); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value, InputIt>::type get(InputIt first, InputIt last) + { + const auto size = std::distance(first, last); + if(0 == size) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next(first, get(0, size - 1)); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get(Container & container) -> + typename std::enable_if::value, + decltype(std::begin(container))>::type + { + return get(std::begin(container), std::end(container)); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + static typename std::enable_if>::value, Container>::type get(A from, + A to, + std::size_t size) + { + Container container; + + container.reserve(size); + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if::value && details::has_reserve>::value, + Container>::type + get(A start, B end, std::size_t size) + { + Container container; + + container.reserve(size); + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + static typename std::enable_if>::value, Container>::type get(A start, + A end, + std::size_t size) + { + Container container; + + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if::value && !details::has_reserve>::value, + Container>::type + get(A start, B end, std::size_t size) + { + Container container; + + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + static typename std::enable_if>::value, Container>::type get(A start, + A end) + { + Container container = {{0}}; + + for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if::value && !details::has_insert>::value, + Container>::type + get(A start, B end) + { + Container container = {{0}}; + + for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T * get(T (&array)[N]) + { + return std::addressof(array[get(0, N - 1)]); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get(Args &&... args) + { + return Dist{std::forward(args)...}(engine_instance()); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get(Dist & dist) + { + return dist(engine_instance()); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle(RandomIt first, RandomIt last) + { + std::shuffle(first, last, engine_instance()); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle(Container & container) + { + shuffle(std::begin(container), std::end(container)); + } + + /// return internal engine by copy + static Engine get_engine() + { + return engine_instance(); + } + + /// return internal engine by ref + static Engine & engine() + { + return engine_instance(); + } + +protected: + /// get reference to the thread local engine instance + static Engine & engine_instance() + { + thread_local Engine engine{Seeder{}()}; + return engine; + } +}; + +/** + * \brief Base template class for random + * with local API and local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ +template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution> +class basic_random_local +{ +public: + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min() + { + return Engine::min(); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max() + { + return Engine::max(); + } + + /// Advances the internal state by z times + void discard(const unsigned long long z) + { + m_engine.discard(z); + } + + /// Reseed by Seeder + void reseed() + { + Seeder seeder; + seed(seeder()); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + void seed(const typename Engine::result_type value = Engine::default_seed) + { + m_engine.seed(value); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + void seed(Sseq & seq) + { + m_engine.seed(seq); + } + + /// return random number from engine in [min(), max()] range + typename Engine::result_type get() + { + return m_engine(); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + bool is_equal(const Engine & other) + { + return m_engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + void serialize(std::basic_ostream & ost) + { + ost << m_engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + void deserialize(std::basic_istream & ist) + { + ist >> m_engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return IntegerDist{from, to}(m_engine); + return IntegerDist{to, from}(m_engine); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return RealDist{from, to}(m_engine); + return RealDist{to, from}(m_engine); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, short, unsigned short>::type; + + return static_cast(get(from, to)); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template::type> + typename std::enable_if::value && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value, + C>::type + get(A from = std::numeric_limits::min(), B to = std::numeric_limits::max()) + { + return get(static_cast(from), static_cast(to)); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) + { + if(from < to) // Allow range from higher to lower + return static_cast( + IntegerDist{static_cast(from), static_cast(to)}(m_engine)); + return static_cast( + IntegerDist{static_cast(to), static_cast(from)}(m_engine)); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + typename std::enable_if::value, bool>::type get(const double probability = 0.5) + { + assert(0 <= probability && 1 >= probability); // out of [0; 1] range + return BoolDist{probability}(m_engine); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + T get(std::initializer_list init_list) + { + assert(0u != init_list.size()); + return *get(init_list.begin(), init_list.end()); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + typename std::enable_if::value, InputIt>::type get(InputIt first, InputIt last) + { + const auto size = std::distance(first, last); + if(0 == size) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next(first, get(0, size - 1)); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + auto get(Container & container) -> + typename std::enable_if::value, + decltype(std::begin(container))>::type + { + return get(std::begin(container), std::end(container)); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + typename std::enable_if>::value, Container>::type get(A from, + A to, + std::size_t size) + { + Container container; + + container.reserve(size); + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if::value && details::has_reserve>::value, + Container>::type + get(A start, B end, std::size_t size) + { + Container container; + + container.reserve(size); + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + typename std::enable_if>::value, Container>::type get(A start, + A end, + std::size_t size) + { + Container container; + + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if::value && !details::has_reserve>::value, + Container>::type + get(A start, B end, std::size_t size) + { + Container container; + + for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + typename std::enable_if>::value, Container>::type get(A start, A end) + { + Container container = {{0}}; + + for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if::value && !details::has_insert>::value, + Container>::type + get(A start, B end) + { + Container container = {{0}}; + + for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + T * get(T (&array)[N]) + { + return std::addressof(array[get(0, N - 1)]); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + typename Dist::result_type get(Args &&... args) + { + return Dist{std::forward(args)...}(m_engine); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + typename Dist::result_type get(Dist & dist) + { + return dist(m_engine); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + void shuffle(RandomIt first, RandomIt last) + { + std::shuffle(first, last, m_engine); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + void shuffle(Container & container) + { + shuffle(std::begin(container), std::end(container)); + } + + /// return internal engine by copy + Engine get_engine() const + { + return m_engine; + } + + /// return internal engine by ref + Engine & engine() + { + return m_engine; + } + +protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine() + { + // Make seeder instance for seed return by reference like std::seed_seq + return Engine{Seeder{}()}; + } + +protected: + /// The random number engine + Engine m_engine{make_seeded_engine()}; +}; + +/** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with static storage + * \note Not thread safe but more prefomance + */ +using random_static = basic_random_static; + +/** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with thread_local storage + * \note Thread safe but less perfomance + */ +using random_thread_local = basic_random_thread_local; + +/** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses non static methods API and data with auto storage + * \note Not thread safe. Should construct on the stack at local scope + */ +using random_local = basic_random_local; } // namespace effolkronium diff --git a/include/mesh_sampling/CLI11.hpp b/include/mesh_sampling/CLI11.hpp index 9fa9cc0..34017a7 100644 --- a/include/mesh_sampling/CLI11.hpp +++ b/include/mesh_sampling/CLI11.hpp @@ -63,581 +63,631 @@ #include #include - #define CLI11_VERSION_MAJOR 2 #define CLI11_VERSION_MINOR 5 #define CLI11_VERSION_PATCH 0 #define CLI11_VERSION "2.5.0" - - - // 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 +# 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 +# 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)]] +# define CLI11_DEPRECATED(reason) [[deprecated(reason)]] #elif defined(_MSC_VER) -#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +# define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) #else -#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +# 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 +#if !defined(CLI11_CPP17) \ + || (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +# define CLI11_NODISCARD #else -#define CLI11_NODISCARD [[nodiscard]] +# 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 +# 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() +# 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__) +# 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__) +# 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 +# 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 +# if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +# define CLI11_HAS_CODECVT 0 +# else +# define CLI11_HAS_CODECVT 1 +# include +# endif #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 +# if defined(CLI11_HAS_CODECVT) +# if CLI11_HAS_CODECVT > 0 +# include +# endif +# else +# define CLI11_HAS_CODECVT 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") +#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\"") +# 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_PUSH __pragma(warning(push)) +# define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) -#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) +# define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) #else -#define CLI11_DIAGNOSTIC_PUSH -#define CLI11_DIAGNOSTIC_POP +# define CLI11_DIAGNOSTIC_PUSH +# define CLI11_DIAGNOSTIC_POP -#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED +# define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED #endif /** Inline macro **/ #ifdef CLI11_COMPILE -#define CLI11_INLINE +# define CLI11_INLINE #else -#define CLI11_INLINE inline +# define CLI11_INLINE inline #endif - - #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -#include // NOLINT(build/include) +# include // NOLINT(build/include) #else -#include -#include +# include +# include #endif - - - #ifdef CLI11_CPP17 -#include -#endif // CLI11_CPP17 +# include +#endif // CLI11_CPP17 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -#include -#include // NOLINT(build/include) -#endif // CLI11_HAS_FILESYSTEM - - +# 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 +# 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 +# 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 +# define NOMINMAX +# include +# undef NOMINMAX +# else +# include +# endif // second -#include +# include // third -#include -#include +# include +# include #endif - -namespace CLI { - +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); +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); +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 +#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 - - +#endif // CLI11_HAS_FILESYSTEM - -namespace detail { +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(); } +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)}; +template +CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F && closure) +{ + return scope_guard_t{std::forward(closure)}; } -#endif // !CLI11_HAS_CODECVT +#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) { +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); +# ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); -#else - 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; +# 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::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); + 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; + return result; -#endif // CLI11_HAS_CODECVT +#endif // CLI11_HAS_CODECVT } -CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +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); +# ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); -#else - 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; +# 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::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); + 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; + return result; -#endif // CLI11_HAS_CODECVT +#endif // CLI11_HAS_CODECVT } CLI11_DIAGNOSTIC_POP -} // namespace detail +} // 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()); } +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::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()); } +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)); } +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 - }; +CLI11_INLINE std::string narrow(std::wstring_view str) +{ + return detail::narrow_impl(str.data(), str.size()); } -#endif // CLI11_HAS_FILESYSTEM - - - +CLI11_INLINE std::wstring widen(std::string_view str) +{ + return detail::widen_impl(str.data(), str.size()); +} +#endif // CLI11_CPP17 -namespace detail { +#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 - -namespace detail { +namespace detail +{ #ifdef _WIN32 -CLI11_INLINE std::vector compute_win32_argv() { - std::vector result; - int argc = 0; +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) + 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())); - } + 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])); - } + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) + { + result.push_back(narrow(wargv[i])); + } - return result; + return result; } #endif -} // namespace detail - - - +} // 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 { +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 - return in << static_cast::type>(item); +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 + return in << static_cast::type>(item); } -} // namespace enums +} // namespace enums /// Export to CLI namespace using enums::operator<<; -namespace detail { +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 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); +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; +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(); +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(); +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); +CLI11_INLINE std::string & ltrim(std::string & str); /// Trim anything from left of string -CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); +CLI11_INLINE std::string & ltrim(std::string & str, const std::string & filter); /// Trim whitespace from right of string -CLI11_INLINE std::string &rtrim(std::string &str); +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); +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)); } +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); } +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); +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); +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); +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); +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); +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); +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 +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')); +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); +CLI11_INLINE bool valid_name_string(const std::string & str); /// Verify an app name -inline bool valid_alias_name_string(const std::string &str) { - static const std::string badChars(std::string("\n") + '\0'); - return (str.find_first_of(badChars) == std::string::npos); +inline bool valid_alias_name_string(const std::string & str) +{ + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); } /// check if a string is a container segment separator (empty or "%%") -inline bool is_separator(const std::string &str) { - static const std::string sep("%%"); - return (str.empty() || str == sep); +inline bool is_separator(const std::string & str) +{ + static const std::string sep("%%"); + return (str.empty() || str == sep); } /// 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()); }); +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; +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; +inline std::string remove_underscore(std::string str) +{ + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; } /// 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); +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); +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, @@ -647,221 +697,260 @@ CLI11_INLINE std::ptrdiff_t find_member(std::string name, /// 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; +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); +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); +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); +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); +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); +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); +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); +CLI11_INLINE std::string binary_escape_string(const std::string & string_to_escape); -CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); +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); +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 = '\''); +CLI11_INLINE bool process_quoted_string(std::string & str, char string_char = '\"', char literal_char = '\''); /// 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::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 & ltrim(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 & ltrim(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('\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 &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 find_and_replace(std::string str, std::string from, std::string to) +{ -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('\n', n); - if(n != std::string::npos) { - input = input.substr(0, n + 1) + leader + input.substr(n + 1); - n += leader.size(); - } - } - return input; -} + std::size_t start_pos = 0; -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; -} + while((start_pos = str.find(from, start_pos)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } -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; + return str; } -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(); +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); } - - return str; + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); } -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); +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; }); } - - return (it != std::end(names)) ? (it - std::begin(names)) : (-1); + 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); } static const std::string escapedChars("\b\t\n\f\r\"\\"); @@ -869,473 +958,587 @@ static const std::string escapedCharsCode("btnfr\"\\"); static const std::string bracketChars{"\"'`[(<{"}; static 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 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); - } +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]); } - 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; + 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); + } } - 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); - } + else + { + out.push_back(*loc); } - return out; + } + 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; - } +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; } - return loc; + 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_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) { +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) { + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) + { case 0: - return close_string_quote(str, start, closure_char); + return close_string_quote(str, start, closure_char); case 1: case 2: case std::string::npos: - return close_literal_quote(str, start, closure_char); + 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; + 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(); + } + } } - if(loc > str.size()) { - loc = str.size(); + 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(); + } } - 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) { - // 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) { - 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 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) +{ + // 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) + { + 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; -} - -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; + } + 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); } - 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); - } + 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 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) { - if(str.size() <= 1) { - return false; - } - if(detail::is_binary_escaped_string(str)) { - str = detail::extract_binary_string(str); - 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); - } - handle_secondary_array(str); - return true; - } - if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { - detail::remove_outer(str, str.front()); - handle_secondary_array(str); - return true; - } +CLI11_INLINE bool process_quoted_string(std::string & str, char string_char, char literal_char) +{ + if(str.size() <= 1) + { return false; + } + if(detail::is_binary_escaped_string(str)) + { + str = detail::extract_binary_string(str); + 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); + } + handle_secondary_array(str); + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) + { + detail::remove_outer(str, str.front()); + handle_secondary_array(str); + return true; + } + return false; } -std::string get_environment_value(const std::string &env_name) { - char *buffer = nullptr; - std::string ename_string; +std::string get_environment_value(const std::string & env_name) +{ + char * buffer = nullptr; + std::string ename_string; #ifdef _MSC_VER - // Windows version - std::size_t sz = 0; - if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { - ename_string = std::string(buffer); - free(buffer); - } + // Windows version + 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 - buffer = std::getenv(env_name.c_str()); - if(buffer != nullptr) { - ename_string = std::string(buffer); - } + // This also works on Windows, but gives a warning + 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(word.length() + charsWritten > paragraphWidth) { - out << '\n' << linePrefix; - charsWritten = 0; - } + return ename_string; +} - out << word << " "; - charsWritten += word.length() + 1; - } +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 - if(!lss.eof()) - out << '\n' << linePrefix; - } - return out; -} + 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(word.length() + charsWritten > paragraphWidth) + { + out << '\n' << linePrefix; + charsWritten = 0; + } -} // namespace detail + 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) {} +#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) {} +#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 { - Success = 0, - IncorrectConstruction = 100, - BadNameString, - OptionAlreadyAdded, - FileError, - ConversionError, - ValidationError, - RequiredError, - RequiresError, - ExcludesError, - ExtrasError, - ConfigError, - InvalidError, - HorribleError, - OptionNotFound, - ArgumentMismatch, - BaseClass = 127 +enum class ExitCodes +{ + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 }; // Error definitions @@ -1347,287 +1550,371 @@ enum class ExitCodes { /// @{ /// 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)) {} +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) +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"); - } +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); - } +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}; - } +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) +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) {} +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) {} +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) {} +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) {} +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) {} +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?)"); } +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"); - } +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) {} +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}; - } +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"); - } +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) {} +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) {} +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::rjoin(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::rjoin(args, " "), - ExitCodes::ExtrasError) {} +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::rjoin(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::rjoin(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"); - } +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) { - } +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) +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) {} +class OptionNotFound : public Error +{ + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} }; #undef CLI11_ERROR_DEF @@ -1635,87 +1922,118 @@ class OptionNotFound : public Error { /// @} - - - // Type tools // Utilities for type enabling -namespace detail { +namespace detail +{ // Based generally on https://rmf.io/cxx11/almost-static-if /// Simple empty scoped class -enum class enabler {}; +enum class enabler +{ +}; /// An instance to use in EnableIf constexpr enabler dummy = {}; -} // namespace detail +} // 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; +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; +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; +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; +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 {}; +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 {}; +template<> +struct is_bool : std::true_type +{ +}; /// Check to see if something is a shared pointer -template struct is_shared_ptr : std::false_type {}; +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 {}; +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 {}; +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; +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; +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; +template<> +struct IsMemberType +{ + using type = std::string; }; -namespace adl_detail { +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 +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; + template + static auto test(...) -> std::false_type; - public: - static constexpr bool value = decltype(test(0))::value; +public: + static constexpr bool value = decltype(test(0))::value; }; -} // namespace adl_detail +} // namespace adl_detail -namespace detail { +namespace detail +{ // These are utilities for IsMember and other transforming objects @@ -1723,55 +2041,72 @@ namespace detail { /// pointer_traits be valid. /// not a pointer -template struct element_type { - using type = T; +template +struct element_type +{ + using type = T; }; -template struct element_type::value>::type> { - using type = typename std::pointer_traits::element_type; +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; +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); - } +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 +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)); - } +: 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 @@ -1781,99 +2116,117 @@ struct pair_adaptor< // 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" +# 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( +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 +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +# pragma nv_diag_suppress 2361 +# else +# pragma diag_suppress 2361 +# endif #endif -#endif - TT{std::declval()} + TT{std::declval()} #ifdef __CUDACC__ -#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ -#pragma nv_diag_default 2361 -#else -#pragma diag_default 2361 +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +# pragma nv_diag_default 2361 +# else +# pragma diag_default 2361 +# endif #endif -#endif - , - std::is_move_assignable()); + , + std::is_move_assignable()); - template static auto test(int, std::false_type) -> std::false_type; + template + static auto test(int, std::false_type) -> std::false_type; - template static auto test(...) -> std::false_type; + template + static auto test(...) -> std::false_type; - public: - static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; }; #ifdef __GNUC__ -#pragma GCC diagnostic pop +# 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 +class is_ostreamable +{ + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); - template static auto test(...) -> std::false_type; + template + static auto test(...) -> std::false_type; - public: - static constexpr bool value = decltype(test(0))::value; +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 +class is_istreamable +{ + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); - template static auto test(...) -> std::false_type; + template + static auto test(...) -> std::false_type; - public: - static constexpr bool value = decltype(test(0))::value; +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 +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; + template + static auto test(...) -> std::false_type; - public: - static constexpr bool value = decltype(test(0))::value; +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) +{ + 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; +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 {}; +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 +template struct is_mutable_container< T, conditional_t().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> {}; + 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 {}; +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 +template struct is_readable_container< T, conditional_t().end()), decltype(std::declval().begin())>, void>> - : public std::true_type {}; +: public std::true_type +{ +}; // check to see if an object is a wrapper (fail by default) -template struct is_wrapper : std::false_type {}; +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 {}; +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; +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}; +template +struct type_count_base +{ + static const int value{0}; }; /// Type size for regular object types that do not look like a tuple -template +template struct type_count_base::value && !is_mutable_container::value && - !std::is_void::value>::type> { - static constexpr int value{1}; + typename std::enable_if::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}; +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}; +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); +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) +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(); +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); +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); +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 < +template< typename T, - enable_if_t::value && !std::is_constructible::value && - !is_ostreamable::value && !is_readable_container::type>::value && - !is_tuple_like::value, + 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 {}; +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, - 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) + "]"}; +template::value && !std::is_constructible::value + && !is_ostreamable::value && is_readable_container::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 +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); +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)); +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; +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{}; +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; +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)); +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{}; +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); +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)); +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::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; +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; +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; +template +struct subtype_count; /// forward declare the subtype_count_min structure -template struct subtype_count_min; +template +struct subtype_count_min; /// This will only trigger for actual void type -template struct type_count { - static const int value{0}; +template +struct type_count +{ + static const int value{0}; }; /// Type size for regular object types that do not look like a tuple -template +template struct type_count::value && !is_tuple_like::value && !is_complex::value && - !std::is_void::value>::type> { - static constexpr int value{1}; + typename std::enable_if::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}; +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}; +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 +template struct type_count::value && !is_complex::value && !is_tuple_like::value && - !is_mutable_container::value>::type> { - static constexpr int value{type_count::value}; + typename std::enable_if::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; +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(); +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>::type> { - static constexpr int value{tuple_type_size()}; +template +struct type_count::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}; +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}; +template +struct type_count_min +{ + static const int value{0}; }; /// Type size for regular object types that do not look like a tuple -template +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}; + 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}; +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 +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}; + 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; +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(); +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>::type> { - static constexpr int value{tuple_type_size_min()}; +template +struct type_count_min::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}; +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}; +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}; +template +struct expected_count< + T, + typename std::enable_if::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}; +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}; +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 : int { - 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, +enum class object_category : int +{ + 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}; +template +struct classify_object +{ + static constexpr object_category value{object_category::other}; }; /// Signed integers -template +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}; + 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 +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}; + typename std::enable_if::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}; +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}; +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}; +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 \ +# define WIDE_STRING_CHECK \ !std::is_assignable::value && !std::is_constructible::value -#define STRING_CHECK true +# define STRING_CHECK true #else -#define WIDE_STRING_CHECK true -#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +# define WIDE_STRING_CHECK true +# define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value #endif /// String and similar direct assignment -template +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}; + 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 +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}; + 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 +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}; + typename std::enable_if::value && !std::is_integral::value + && STRING_CHECK && std::is_assignable::value>::type> +{ + static constexpr object_category value{object_category::wstring_assignable}; }; -template +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}; + 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::enumeration}; }; -template struct classify_object::value>::type> { - static constexpr object_category value{object_category::complex_number}; +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; +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 +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}; + typename std::enable_if<(!is_mutable_container::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 +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}; + typename std::enable_if::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 +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}; + typename std::enable_if::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 +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}; + typename std::enable_if::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 +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 + 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}; +template +struct classify_object::value>::type> +{ + static constexpr object_category value{object_category::container_value}; }; // Type name print @@ -2434,1365 +2882,1597 @@ template struct classify_object::value == object_category::char_value, detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "CHAR"; +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::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::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"; +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"; +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"; +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"; +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"; +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 +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 +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>(); +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{}; +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; +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; +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(); +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}; +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(input.find_first_of("_'") != std::string::npos) + { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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; - std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); - if(errno == ERANGE) { - return false; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) + { + return false; } output = static_cast(output_ll); - if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { - return true; - } + 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; - 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(input.find_first_of("_'") != std::string::npos) { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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 + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) + { + return false; } - 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; +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 and trailing spaces + if(input.find_first_of("_'") != std::string::npos) + { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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; - std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); - if(errno == ERANGE) { - return false; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + 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 and trailing spaces - if(input.find_first_of("_'") != std::string::npos) { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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 (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; } - 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") { +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; - } 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; - } + 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); +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; - } - return integral_conversion(input, output); +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; + } + return integral_conversion(input, output); } /// 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; +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(input.find_first_of("_'") != std::string::npos) { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); - return lexical_cast(nstring, output); - } +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(input.find_first_of("_'") != std::string::npos) + { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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}; - } +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}; } - if(worked) { - output = T{x, y}; - return worked; + else + { + worked = lexical_cast(str1, x); + y = XC{0}; } - return from_stream(input, output); + } + 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; +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 < +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; +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::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & input, T & output) +{ + output = widen(input); + return true; } -template < +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; +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; +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); +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 < +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; - } +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; - } + double dval = 0.0; + if(lexical_cast(input, dval)) + { + output = T{dval}; + return true; + } - return from_stream(input, output); + return from_stream(input, output); } /// Assignable from int -template < +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); +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 < +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); +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)) { +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) +# 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; + // 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) +# 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 + 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); +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; +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); +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; - } +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 + 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); +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)) { +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" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-conversion" #endif - output = val; + output = val; #if defined(__clang__) -#pragma clang diagnostic pop +# pragma clang diagnostic pop #endif - return true; - } - return false; + 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; +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; +template::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); +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; +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()); +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; +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(); } - return lexical_assign(strings[0], output); + 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) { +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; + 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); +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); +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 +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; +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; +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 +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; + 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, +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; + 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, +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_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; +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; - } +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()); + } + 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); +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; - } +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; + } + 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; +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; +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; +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); } - if(fail) { - for(const auto &arg : values) { - output.append(arg); - } - } else { - std::ostringstream out; - out.precision(16); - out << val; - output = out.str(); + val += tv; + } + if(fail) + { + for(const auto & arg : values) + { + output.append(arg); } - return output; + } + else + { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; } -} // namespace detail - +} // namespace detail - -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); +CLI11_INLINE bool split_short(const std::string & current, 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); +CLI11_INLINE bool split_long(const std::string & current, 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); +CLI11_INLINE bool split_windows_style(const std::string & current, 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); +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 +CLI11_INLINE std::tuple, std::vector, std::string> get_names( + const std::vector & input, + bool allow_non_standard = false); +} // namespace detail +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_short(const std::string & current, 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; +CLI11_INLINE bool split_long(const std::string & current, 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); } - 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; + else + { + name = current.substr(2); + value = ""; } - return false; + 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); +CLI11_INLINE bool split_windows_style(const std::string & current, 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); } - 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); + else + { + name = current.substr(1); + value = ""; } - 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.length() == 0) { - 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 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.length() == 0) + { + 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); } - return std::make_tuple(short_names, long_names, pos_name); -} - -} // namespace detail - - + 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 - } +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 { - std::ifstream input{name}; - if(!input.good()) - throw FileError::Missing(name); - - return from_config(input); - } - - /// Virtual destructor - virtual ~Config() = default; +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 + { + std::ifstream input{name}; + 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; - } +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 & sectionRef() + { + return configSection; + } + /// get the section + CLI11_NODISCARD const std::string & section() const + { + return configSection; + } + /// specify a particular section of the configuration file to use + ConfigBase * section(const std::string & sectionName) + { + 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 ConfigINI : public ConfigTOML +{ + +public: + ConfigINI() + { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } }; - - class Option; /// @defgroup validator_group Validators @@ -3805,180 +4485,222 @@ class Option; /// @{ /// -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{}; +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_(); } - /// 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); + 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); }; /// Class wrapping some of the accessors of Validator -class CustomValidator : public Validator { - public: +class CustomValidator : public Validator +{ +public: }; // 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 { +namespace detail +{ /// CLI enumeration of different file types -enum class path_type { nonexistent, file, directory }; +enum class path_type +{ + nonexistent, + file, + directory +}; /// get the type of the path from a file name -CLI11_INLINE path_type check_path(const char *file) noexcept; +CLI11_INLINE path_type check_path(const char * file) noexcept; /// Check for an existing file (returns error message if check fails) -class ExistingFileValidator : public Validator { - public: - ExistingFileValidator(); +class ExistingFileValidator : public Validator +{ +public: + ExistingFileValidator(); }; /// Check for an existing directory (returns error message if check fails) -class ExistingDirectoryValidator : public Validator { - public: - ExistingDirectoryValidator(); +class ExistingDirectoryValidator : public Validator +{ +public: + ExistingDirectoryValidator(); }; /// Check for an existing path -class ExistingPathValidator : public Validator { - public: - ExistingPathValidator(); +class ExistingPathValidator : public Validator +{ +public: + ExistingPathValidator(); }; /// Check for an non-existing path -class NonexistentPathValidator : public Validator { - public: - NonexistentPathValidator(); +class NonexistentPathValidator : public Validator +{ +public: + NonexistentPathValidator(); }; /// Validate the given string is a legal ipv4 address -class IPV4Validator : public Validator { - public: - IPV4Validator(); +class IPV4Validator : public Validator +{ +public: + IPV4Validator(); }; -class EscapedStringTransformer : public Validator { - public: - EscapedStringTransformer(); +class EscapedStringTransformer : public Validator +{ +public: + EscapedStringTransformer(); }; -} // namespace detail +} // namespace detail // Static is not needed here, because global const implies static. @@ -4001,18 +4723,25 @@ const detail::IPV4Validator ValidIPV4; const detail::EscapedStringTransformer EscapedString; /// 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)) { +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()) {} + } + return std::string(); + }) + { + } + TypeValidator() : TypeValidator(detail::type_name()) {} }; /// Check for a number @@ -4020,44 +4749,52 @@ const TypeValidator Number("NUMBER"); /// 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); +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) {} +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 @@ -4067,394 +4804,480 @@ const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); /// 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{}; - }; - } +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) {} + /// Range of one value is 0 to value + template + explicit Bound(T max_val) : Bound(static_cast(0), max_val) + { + } }; -namespace detail { -template ::type>::value, detail::enabler> = detail::dummy> -auto smart_deref(T value) -> decltype(*value) { - return *value; +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) { - return value; +template::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type & smart_deref(T & value) +{ + 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; +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; +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}; +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}; +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}; +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}; } // 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)); +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); +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; - } +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; +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; } -} // namespace detail +} // 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)); - }; - } +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{}; + } - /// 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...) {} + // 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>; +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...) {} +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::to_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"; - }; - } +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::to_string(detail::pair_adaptor::second(v)); }, ","); + out.push_back('}'); + return out; + }; - /// 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...) {} + 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); } +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); } +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; +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. @@ -4468,137 +5291,166 @@ inline std::string ignore_space(std::string item) { /// 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 { - 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); +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 + { + 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; } - - /// 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(); + 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)); +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. @@ -4612,156 +5464,175 @@ inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, c /// "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); +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); }; -namespace detail { +namespace detail +{ /// 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 +} // 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); - } +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); } - return retstring; + 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_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; +CLI11_INLINE Validator Validator::operator&(const Validator & other) const +{ + Validator newval; - newval._merge_description(*this, other, " AND "); + 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_; + // 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.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; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; } -CLI11_INLINE Validator Validator::operator|(const Validator &other) const { - Validator newval; +CLI11_INLINE Validator Validator::operator|(const Validator & other) const +{ + Validator newval; - newval._merge_description(*this, other, " OR "); + 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_; + // 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(); + 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; + 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_; +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; + 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) { +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_; + 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 + ')'; - }; + 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 { +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 +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 + return path_type::nonexistent; // LCOV_EXCL_LINE case std::filesystem::file_type::directory: - return path_type::directory; + return path_type::directory; case std::filesystem::file_type::symlink: case std::filesystem::file_type::block: case std::filesystem::file_type::character: @@ -4770,417 +5641,506 @@ CLI11_INLINE path_type check_path(const char *file) noexcept { case std::filesystem::file_type::regular: case std::filesystem::file_type::unknown: default: - return path_type::file; - } + 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; +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 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 IPV4Validator::IPV4Validator() : Validator("IPV4") +{ + func_ = [](std::string & ip_addr) + { + 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{}; + }; +} + +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{}; + }; +} + +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 +{ + +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 +/// @} -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(); - }; -} +class Option; +class App; -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(); - }; -} +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. -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 IPV4Validator::IPV4Validator() : Validator("IPV4") { - func_ = [](std::string &ip_addr) { - 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{}; - }; -} - -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{}; - }; -} - -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 { - -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 -/// @} - - - - -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 { - Normal, ///< The normal, detailed help - All, ///< A fully expanded help - Sub, ///< Used when printed as part of expanded subcommand -}; +enum class AppFormatMode +{ + 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 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}; - - /// @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" label - 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 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; } - - ///@} - /// @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_; } - - ///@} +class FormatterBase +{ +protected: + /// @name Options + ///@{ + + /// The width of the left column (options/flags/subcommands) + std::size_t column_width_{30}; + + /// 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}; + + /// @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" label + 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 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; + } + + ///@} + /// @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_; + } + + ///@} }; /// This is a specialty override for lambda functions -class FormatterLambda final : public FormatterBase { - using funct_t = std::function; +class FormatterLambda final : public FormatterBase +{ + using funct_t = std::function; - /// The lambda to hold and run - funct_t lambda_; + /// 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)) {} +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) + /// 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 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; +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 - ///@{ + /// @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 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 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 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 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 + 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 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 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 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 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; + /// This puts everything together + std::string make_help(const App * app, std::string, AppFormatMode mode) const override; - ///@} - /// @name Options - ///@{ + ///@} + /// @name Options + ///@{ - /// This prints out an option help line, either positional or optional form - virtual std::string make_option(const Option *, bool) const; + /// 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 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 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 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; + /// @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; @@ -5190,6338 +6150,7742 @@ class App; using Option_p = std::unique_ptr::value - && details::is_supported_number::value - // Prevent implicit type conversion from singed to unsigned types - && std::is_signed::value != std::is_unsigned::value, - C>::type - get(A from = std::numeric_limits::min(), B to = std::numeric_limits::max()) - { - return get(static_cast(from), static_cast(to)); - } - - /** - * \brief Generate a random character in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random character in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value, T>::type get( - T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return static_cast( - IntegerDist{static_cast(from), static_cast(to)}(engine_instance())); - return static_cast( - IntegerDist{static_cast(to), static_cast(from)}(engine_instance())); - } - - /** - * \brief Generate a bool value with specific probability - * by std::bernoulli_distribution - * \param probability The probability of generating true in [0; 1] range - * 0 means always false, 1 means always true - * \return 'true' with 'probability' probability ('false' otherwise) - */ - template - static typename std::enable_if::value, bool>::type get(const double probability = 0.5) - { - assert(0 <= probability && 1 >= probability); // out of [0; 1] range - return BoolDist{probability}(engine_instance()); - } - - /** - * \brief Return random value from initilizer_list - * \param init_list initilizer_list with values - * \return Random value from initilizer_list - * \note Should be 1 or more elements in initilizer_list - * \note Warning! Elements in initilizer_list can't be moved: - * https://stackoverflow.com/a/8193157/5734836 - */ - template - static T get(std::initializer_list init_list) - { - assert(0u != init_list.size()); - return *get(init_list.begin(), init_list.end()); - } - - /** - * \brief Return random iterator from iterator range - * \param first, last - the range of elements - * \return Random iterator from [first, last) range - * \note If first == last, return last - */ - template - static typename std::enable_if::value, InputIt>::type get(InputIt first, InputIt last) - { - const auto size = std::distance(first, last); - if(0 == size) return last; - using diff_t = typename std::iterator_traits::difference_type; - return std::next(first, get(0, size - 1)); - } - - /** - * \brief Return random iterator from Container - * \param container The container with elements - * \return Random iterator from container - * \note If container is empty return std::end( container ) iterator - */ - template - static auto get(Container & container) -> - typename std::enable_if::value, - decltype(std::begin(container))>::type - { - return get(std::begin(container), std::end(container)); - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, typename A> - static typename std::enable_if>::value, Container>::type get(A from, - A to, - std::size_t size) - { - Container container; - - container.reserve(size); - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(from, to)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if::value && details::has_reserve>::value, - Container>::type - get(A start, B end, std::size_t size) - { - Container container; - - container.reserve(size); - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, typename A> - static typename std::enable_if>::value, Container>::type get(A start, - A end, - std::size_t size) - { - Container container; - - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if::value && !details::has_reserve>::value, - Container>::type - get(A start, B end, std::size_t size) - { - Container container; - - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return array-like container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param N The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, std::size_t N, typename A> - static typename std::enable_if>::value, Container>::type get(A start, - A end) - { - Container container = {{0}}; - - for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); - - return container; - } - - /** - * \brief Return array-like container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, - std::size_t N, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if::value && !details::has_insert>::value, - Container>::type - get(A start, B end) - { - Container container = {{0}}; - - for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); - - return container; - } - - /** - * \brief Return random pointer from built-in array - * \param array The built-in array with elements - * \return Pointer to random element in array - */ - template - static T * get(T (&array)[N]) - { - return std::addressof(array[get(0, N - 1)]); - } - - /** - * \brief Return value from custom Dist distribution - * seeded by internal random engine - * \param Dist The type of custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom distribution - */ - template - static typename Dist::result_type get(Args &&... args) - { - return Dist{std::forward(args)...}(engine_instance()); - } - - /** - * \brief Return value from custom 'dist' distribution - * seeded by internal random engine - * \param dist The custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom 'dist' distribution - */ - template - static typename Dist::result_type get(Dist & dist) - { - return dist(engine_instance()); - } - - /** - * \brief Reorders the elements in the given range [first, last) - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param first, last - the range of elements to shuffle randomly - */ - template - static void shuffle(RandomIt first, RandomIt last) - { - std::shuffle(first, last, engine_instance()); - } - - /** - * \brief Reorders the elements in the given container - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param container - the container with elements to shuffle randomly - */ - template - static void shuffle(Container & container) - { - shuffle(std::begin(container), std::end(container)); - } - - /// return internal engine by copy - static Engine get_engine() - { - return engine_instance(); - } - - /// return internal engine by ref - static Engine & engine() - { - return engine_instance(); - } - -protected: - /// get reference to the static engine instance - static Engine & engine_instance() - { - static Engine engine{Seeder{}()}; - return engine; - } -}; - -/** - * \brief Base template class for random - * with thread_local API and thread_local internal member storage - * \note it IS thread safe but less efficient then - * basic_random_static - * \param Engine A random engine with interface like in the std::mt19937 - * \param Seeder A seeder type which return seed for internal engine - * through operator() - */ -template class IntegerDist = std::uniform_int_distribution, - template class RealDist = std::uniform_real_distribution, - typename BoolDist = std::bernoulli_distribution> -class basic_random_thread_local -{ -public: - basic_random_thread_local() = delete; - - /// Type of used random number engine - using engine_type = Engine; - - /// Type of used random number seeder - using seeder_type = Seeder; - - /// Type of used integer distribution - template - using integer_dist_t = IntegerDist; - - /// Type of used real distribution - template - using real_dist_t = RealDist; - - /// Type of used bool distribution - using bool_dist_t = BoolDist; - - /// Key type for getting common type numbers or objects - using common = details::common; - - /** - * \return The minimum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type min() - { - return Engine::min(); - } - - /** - * \return The maximum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type max() - { - return Engine::max(); - } - - /// Advances the internal state by z times - static void discard(const unsigned long long z) - { - engine_instance().discard(z); - } - - /// Reseed by Seeder - static void reseed() - { - Seeder seeder; - seed(seeder()); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param value The seed value to use - * in the initialization of the internal state - */ - static void seed(const typename Engine::result_type value = Engine::default_seed) - { - engine_instance().seed(value); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param seq The seed sequence - * to use in the initialization of the internal state - */ - template - static void seed(Sseq & seq) - { - engine_instance().seed(seq); - } - - /// return random number from engine in [min(), max()] range - static typename Engine::result_type get() - { - return engine_instance()(); - } - - /** - * \brief Compares internal pseudo-random number engine - * with 'other' pseudo-random number engine. - * Two engines are equal, if their internal states - * are equivalent, that is, if they would generate - * equivalent values for any number of calls of operator() - * \param other The engine, with which the internal engine will be compared - * \return true, if other and internal engine are equal - */ - static bool is_equal(const Engine & other) - { - return engine_instance() == other; - } - - /** - * \brief Serializes the internal state of the - * internal pseudo-random number engine as a sequence - * of decimal numbers separated by one or more spaces, - * and inserts it to the stream ost. The fill character - * and the formatting flags of the stream are - * ignored and unaffected. - * \param ost The output stream to insert the data to - */ - template - static void serialize(std::basic_ostream & ost) - { - ost << engine_instance(); - } - - /** - * \brief Restores the internal state of the - * internal pseudo-random number engine from - * the serialized representation, which - * was created by an earlier call to 'serialize' - * using a stream with the same imbued locale and - * the same CharT and Traits. - * If the input cannot be deserialized, - * internal engine is left unchanged and failbit is raised on ist - * \param ost The input stream to extract the data from - */ - template - static void deserialize(std::basic_istream & ist) - { - ist >> engine_instance(); - } - - /** - * \brief Generate a random integer number in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random integer number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return IntegerDist{from, to}(engine_instance()); - return IntegerDist{to, from}(engine_instance()); - } - - /** - * \brief Generate a random real number in a [from; to] range - * by std::uniform_real_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random real number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return RealDist{from, to}(engine_instance()); - return RealDist{to, from}(engine_instance()); - } - - /** - * \brief Generate a random byte number in a [from; to] range - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random byte number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - // Choose between short and unsigned short for byte conversion - using short_t = typename std::conditional::value, short, unsigned short>::type; - - return static_cast(get(from, to)); - } - - /** - * \brief Generate a random common_type number in a [from; to] range - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random common_type number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Allow implicit type conversion - * \note Prevent implicit type conversion from singed to unsigned types - * Why? std::common_type chooses unsigned value, - * then Signed value will be converted to Unsigned value - * which gives us a wrong range for random values. - * https://stackoverflow.com/a/5416498/5734836 - */ - template::type> - static typename std::enable_if::value && details::is_supported_number::value - && details::is_supported_number::value - // Prevent implicit type conversion from singed to unsigned types - && std::is_signed::value != std::is_unsigned::value, - C>::type - get(A from = std::numeric_limits::min(), B to = std::numeric_limits::max()) - { - return get(static_cast(from), static_cast(to)); - } - - /** - * \brief Generate a random character in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random character in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - static typename std::enable_if::value, T>::type get( - T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return static_cast( - IntegerDist{static_cast(from), static_cast(to)}(engine_instance())); - return static_cast( - IntegerDist{static_cast(to), static_cast(from)}(engine_instance())); - } - - /** - * \brief Generate a bool value with specific probability - * by std::bernoulli_distribution - * \param probability The probability of generating true in [0; 1] range - * 0 means always false, 1 means always true - * \return 'true' with 'probability' probability ('false' otherwise) - */ - template - static typename std::enable_if::value, bool>::type get(const double probability = 0.5) - { - assert(0 <= probability && 1 >= probability); // out of [0; 1] range - return BoolDist{probability}(engine_instance()); - } - - /** - * \brief Return random value from initilizer_list - * \param init_list initilizer_list with values - * \return Random value from initilizer_list - * \note Should be 1 or more elements in initilizer_list - * \note Warning! Elements in initilizer_list can't be moved: - * https://stackoverflow.com/a/8193157/5734836 - */ - template - static T get(std::initializer_list init_list) - { - assert(0u != init_list.size()); - return *get(init_list.begin(), init_list.end()); - } - - /** - * \brief Return random iterator from iterator range - * \param first, last - the range of elements - * \return Random iterator from [first, last) range - * \note If first == last, return last - */ - template - static typename std::enable_if::value, InputIt>::type get(InputIt first, InputIt last) - { - const auto size = std::distance(first, last); - if(0 == size) return last; - using diff_t = typename std::iterator_traits::difference_type; - return std::next(first, get(0, size - 1)); - } - - /** - * \brief Return random iterator from Container - * \param container The container with elements - * \return Random iterator from container - * \note If container is empty return std::end( container ) iterator - */ - template - static auto get(Container & container) -> - typename std::enable_if::value, - decltype(std::begin(container))>::type - { - return get(std::begin(container), std::end(container)); - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, typename A> - static typename std::enable_if>::value, Container>::type get(A from, - A to, - std::size_t size) - { - Container container; - - container.reserve(size); - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(from, to)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if::value && details::has_reserve>::value, - Container>::type - get(A start, B end, std::size_t size) - { - Container container; - - container.reserve(size); - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, typename A> - static typename std::enable_if>::value, Container>::type get(A start, - A end, - std::size_t size) - { - Container container; - - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if::value && !details::has_reserve>::value, - Container>::type - get(A start, B end, std::size_t size) - { - Container container; - - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return array-like container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param N The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, std::size_t N, typename A> - static typename std::enable_if>::value, Container>::type get(A start, - A end) - { - Container container = {{0}}; - - for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); - - return container; - } - - /** - * \brief Return array-like container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, - std::size_t N, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - static typename std::enable_if::value && !details::has_insert>::value, - Container>::type - get(A start, B end) - { - Container container = {{0}}; - - for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); - - return container; - } - - /** - * \brief Return random pointer from built-in array - * \param array The built-in array with elements - * \return Pointer to random element in array - */ - template - static T * get(T (&array)[N]) - { - return std::addressof(array[get(0, N - 1)]); - } - - /** - * \brief Return value from custom Dist distribution - * seeded by internal random engine - * \param Dist The type of custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom distribution - */ - template - static typename Dist::result_type get(Args &&... args) - { - return Dist{std::forward(args)...}(engine_instance()); - } - - /** - * \brief Return value from custom 'dist' distribution - * seeded by internal random engine - * \param dist The custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom 'dist' distribution - */ - template - static typename Dist::result_type get(Dist & dist) - { - return dist(engine_instance()); - } - - /** - * \brief Reorders the elements in the given range [first, last) - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param first, last - the range of elements to shuffle randomly - */ - template - static void shuffle(RandomIt first, RandomIt last) - { - std::shuffle(first, last, engine_instance()); - } - - /** - * \brief Reorders the elements in the given container - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param container - the container with elements to shuffle randomly - */ - template - static void shuffle(Container & container) - { - shuffle(std::begin(container), std::end(container)); - } - - /// return internal engine by copy - static Engine get_engine() - { - return engine_instance(); - } - - /// return internal engine by ref - static Engine & engine() - { - return engine_instance(); - } - -protected: - /// get reference to the thread local engine instance - static Engine & engine_instance() - { - thread_local Engine engine{Seeder{}()}; - return engine; - } -}; - -/** - * \brief Base template class for random - * with local API and local internal member storage - * \note it IS thread safe but less efficient then - * basic_random_static - * \param Engine A random engine with interface like in the std::mt19937 - * \param Seeder A seeder type which return seed for internal engine - * through operator() - */ -template class IntegerDist = std::uniform_int_distribution, - template class RealDist = std::uniform_real_distribution, - typename BoolDist = std::bernoulli_distribution> -class basic_random_local -{ -public: - /// Type of used random number engine - using engine_type = Engine; - - /// Type of used random number seeder - using seeder_type = Seeder; - - /// Type of used integer distribution - template - using integer_dist_t = IntegerDist; - - /// Type of used real distribution - template - using real_dist_t = RealDist; - - /// Type of used bool distribution - using bool_dist_t = BoolDist; - - /// Key type for getting common type numbers or objects - using common = details::common; - - /** - * \return The minimum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type min() - { - return Engine::min(); - } - - /** - * \return The maximum value - * potentially generated by the random-number engine - */ - static constexpr typename Engine::result_type max() - { - return Engine::max(); - } - - /// Advances the internal state by z times - void discard(const unsigned long long z) - { - m_engine.discard(z); - } - - /// Reseed by Seeder - void reseed() - { - Seeder seeder; - seed(seeder()); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param value The seed value to use - * in the initialization of the internal state - */ - void seed(const typename Engine::result_type value = Engine::default_seed) - { - m_engine.seed(value); - } - - /** - * \brief Reinitializes the internal state - * of the random-number engine using new seed value - * \param seq The seed sequence - * to use in the initialization of the internal state - */ - template - void seed(Sseq & seq) - { - m_engine.seed(seq); - } - - /// return random number from engine in [min(), max()] range - typename Engine::result_type get() - { - return m_engine(); - } - - /** - * \brief Compares internal pseudo-random number engine - * with 'other' pseudo-random number engine. - * Two engines are equal, if their internal states - * are equivalent, that is, if they would generate - * equivalent values for any number of calls of operator() - * \param other The engine, with which the internal engine will be compared - * \return true, if other and internal engine are equal - */ - bool is_equal(const Engine & other) - { - return m_engine == other; - } - - /** - * \brief Serializes the internal state of the - * internal pseudo-random number engine as a sequence - * of decimal numbers separated by one or more spaces, - * and inserts it to the stream ost. The fill character - * and the formatting flags of the stream are - * ignored and unaffected. - * \param ost The output stream to insert the data to - */ - template - void serialize(std::basic_ostream & ost) - { - ost << m_engine; - } - - /** - * \brief Restores the internal state of the - * internal pseudo-random number engine from - * the serialized representation, which - * was created by an earlier call to 'serialize' - * using a stream with the same imbued locale and - * the same CharT and Traits. - * If the input cannot be deserialized, - * internal engine is left unchanged and failbit is raised on ist - * \param ost The input stream to extract the data from - */ - template - void deserialize(std::basic_istream & ist) - { - ist >> m_engine; - } - - /** - * \brief Generate a random integer number in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random integer number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return IntegerDist{from, to}(m_engine); - return IntegerDist{to, from}(m_engine); - } - - /** - * \brief Generate a random real number in a [from; to] range - * by std::uniform_real_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random real number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return RealDist{from, to}(m_engine); - return RealDist{to, from}(m_engine); - } - - /** - * \brief Generate a random byte number in a [from; to] range - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random byte number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - // Choose between short and unsigned short for byte conversion - using short_t = typename std::conditional::value, short, unsigned short>::type; - - return static_cast(get(from, to)); - } - - /** - * \brief Generate a random common_type number in a [from; to] range - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random common_type number in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Allow implicit type conversion - * \note Prevent implicit type conversion from singed to unsigned types - * Why? std::common_type chooses unsigned value, - * then Signed value will be converted to Unsigned value - * which gives us a wrong range for random values. - * https://stackoverflow.com/a/5416498/5734836 - */ - template::type> - typename std::enable_if::value && details::is_supported_number::value - && details::is_supported_number::value - // Prevent implicit type conversion from singed to unsigned types - && std::is_signed::value != std::is_unsigned::value, - C>::type - get(A from = std::numeric_limits::min(), B to = std::numeric_limits::max()) - { - return get(static_cast(from), static_cast(to)); - } - - /** - * \brief Generate a random character in a [from; to] range - * by std::uniform_int_distribution - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \return A random character in a [from; to] range - * \note Allow both: 'from' <= 'to' and 'from' >= 'to' - * \note Prevent implicit type conversion - */ - template - typename std::enable_if::value, T>::type get(T from = std::numeric_limits::min(), - T to = std::numeric_limits::max()) - { - if(from < to) // Allow range from higher to lower - return static_cast( - IntegerDist{static_cast(from), static_cast(to)}(m_engine)); - return static_cast( - IntegerDist{static_cast(to), static_cast(from)}(m_engine)); - } - - /** - * \brief Generate a bool value with specific probability - * by std::bernoulli_distribution - * \param probability The probability of generating true in [0; 1] range - * 0 means always false, 1 means always true - * \return 'true' with 'probability' probability ('false' otherwise) - */ - template - typename std::enable_if::value, bool>::type get(const double probability = 0.5) - { - assert(0 <= probability && 1 >= probability); // out of [0; 1] range - return BoolDist{probability}(m_engine); - } - - /** - * \brief Return random value from initilizer_list - * \param init_list initilizer_list with values - * \return Random value from initilizer_list - * \note Should be 1 or more elements in initilizer_list - * \note Warning! Elements in initilizer_list can't be moved: - * https://stackoverflow.com/a/8193157/5734836 - */ - template - T get(std::initializer_list init_list) - { - assert(0u != init_list.size()); - return *get(init_list.begin(), init_list.end()); - } - - /** - * \brief Return random iterator from iterator range - * \param first, last - the range of elements - * \return Random iterator from [first, last) range - * \note If first == last, return last - */ - template - typename std::enable_if::value, InputIt>::type get(InputIt first, InputIt last) - { - const auto size = std::distance(first, last); - if(0 == size) return last; - using diff_t = typename std::iterator_traits::difference_type; - return std::next(first, get(0, size - 1)); - } - - /** - * \brief Return random iterator from Container - * \param container The container with elements - * \return Random iterator from container - * \note If container is empty return std::end( container ) iterator - */ - template - auto get(Container & container) -> - typename std::enable_if::value, - decltype(std::begin(container))>::type - { - return get(std::begin(container), std::end(container)); - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, typename A> - typename std::enable_if>::value, Container>::type get(A from, - A to, - std::size_t size) - { - Container container; - - container.reserve(size); - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(from, to)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - * \note Container "reserve" method will be called before generation - */ - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - typename std::enable_if::value && details::has_reserve>::value, - Container>::type - get(A start, B end, std::size_t size) - { - Container container; - - container.reserve(size); - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, typename A> - typename std::enable_if>::value, Container>::type get(A start, - A end, - std::size_t size) - { - Container container; - - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - typename std::enable_if::value && !details::has_reserve>::value, - Container>::type - get(A start, B end, std::size_t size) - { - Container container; - - for(std::size_t i = 0; i < size; ++i) container.insert(std::end(container), get(start, end)); - - return container; - } - - /** - * \brief Return array-like container filled with random values - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param N The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, std::size_t N, typename A> - typename std::enable_if>::value, Container>::type get(A start, A end) - { - Container container = {{0}}; - - for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); - - return container; - } - - /** - * \brief Return array-like container filled with random common_type values - * \param Key The Key type for this version of 'get' method - * Type should be '(THIS_TYPE)::common' struct - * \param from The first limit number of a random range - * \param to The second limit number of a random range - * \param size The number of elements in resulting container - * \return Container filled with random values - */ - template class Container, - std::size_t N, - typename Key, - typename A, - typename B, - typename C = typename std::common_type::type> - typename std::enable_if::value && !details::has_insert>::value, - Container>::type - get(A start, B end) - { - Container container = {{0}}; - - for(std::size_t i = 0; i < N; ++i) container[i] = get(start, end); - - return container; - } - - /** - * \brief Return random pointer from built-in array - * \param array The built-in array with elements - * \return Pointer to random element in array - */ - template - T * get(T (&array)[N]) - { - return std::addressof(array[get(0, N - 1)]); - } - - /** - * \brief Return value from custom Dist distribution - * seeded by internal random engine - * \param Dist The type of custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom distribution - */ - template - typename Dist::result_type get(Args &&... args) - { - return Dist{std::forward(args)...}(m_engine); - } - - /** - * \brief Return value from custom 'dist' distribution - * seeded by internal random engine - * \param dist The custom distribution with next concept: - * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution - * \param args The arguments which will be forwarded to Dist constructor - * \return Value from custom 'dist' distribution - */ - template - typename Dist::result_type get(Dist & dist) - { - return dist(m_engine); - } - - /** - * \brief Reorders the elements in the given range [first, last) - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param first, last - the range of elements to shuffle randomly - */ - template - void shuffle(RandomIt first, RandomIt last) - { - std::shuffle(first, last, m_engine); - } - - /** - * \brief Reorders the elements in the given container - * such that each possible permutation of those elements - * has equal probability of appearance. - * \param container - the container with elements to shuffle randomly - */ - template - void shuffle(Container & container) - { - shuffle(std::begin(container), std::end(container)); - } - - /// return internal engine by copy - Engine get_engine() const - { - return m_engine; - } - - /// return internal engine by ref - Engine & engine() - { - return m_engine; - } - -protected: - /// return engine seeded by Seeder - static Engine make_seeded_engine() - { - // Make seeder instance for seed return by reference like std::seed_seq - return Engine{Seeder{}()}; - } - -protected: - /// The random number engine - Engine m_engine{make_seeded_engine()}; -}; - -/** - * \brief The basic static random alias based on a std::mt19937 - * \note It uses static methods API and data with static storage - * \note Not thread safe but more prefomance - */ -using random_static = basic_random_static; - -/** - * \brief The basic static random alias based on a std::mt19937 - * \note It uses static methods API and data with thread_local storage - * \note Thread safe but less perfomance - */ -using random_thread_local = basic_random_thread_local; - -/** - * \brief The basic static random alias based on a std::mt19937 - * \note It uses non static methods API and data with auto storage - * \note Not thread safe. Should construct on the stack at local scope - */ -using random_local = basic_random_local; +namespace effolkronium { + + namespace details { + /// Key type for getting common type numbers or objects + struct common{ }; + + /// True if type T is applicable by a std::uniform_int_distribution + template + struct is_uniform_int { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is applicable by a std::uniform_real_distribution + template + struct is_uniform_real { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is plain byte + template + struct is_byte { + static constexpr bool value = + std::is_same::value + || std::is_same::value; + }; + + /// True if type T is plain number type + template + struct is_supported_number { + static constexpr bool value = + is_byte ::value + || is_uniform_real::value + || is_uniform_int ::value; + }; + + /// True if type T is character type + template + struct is_supported_character { + static constexpr bool value = + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value; + }; + + /// True if type T is iterator + template + struct is_iterator { + private: + static char test( ... ); + + template ::difference_type, + typename = typename std::iterator_traits::pointer, + typename = typename std::iterator_traits::reference, + typename = typename std::iterator_traits::value_type, + typename = typename std::iterator_traits::iterator_category + > static long test( U&& ); + public: + static constexpr bool value = std::is_same< + decltype( test( std::declval( ) ) ), long>::value; + }; + + template + class has_reserve + { + private: + template + static char test(...); + + template + static long test(decltype(&C::reserve)); + public: + static constexpr bool value = std::is_same< + decltype(test(0)), long>::value; + }; + + template + class has_insert + { + private: + template + static char test(...); + + template + static long test(decltype(&C::insert)); + public: + static constexpr bool value = std::is_same< + decltype(test(0)), long>::value; + }; + + } // namespace details + + /// Default seeder for 'random' classes + struct seeder_default { + /// return seed sequence + std::seed_seq& operator() ( ) { + // MinGW issue, std::random_device returns constant value + // Use std::seed_seq with additional seed from C++ chrono + return seed_seq; + } + private: + std::seed_seq seed_seq{ { + static_cast( std::random_device{ }( ) ), + static_cast( std::chrono::steady_clock::now( ) + .time_since_epoch( ).count( ) ), + } }; + }; + + /** + * \brief Base template class for random + * with static API and static internal member storage + * \note it is NOT thread safe but more efficient then + * basic_random_thread_local + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_static { + public: + basic_random_static( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine_instance( ).discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine_instance( ).seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed( Sseq& seq ) { + engine_instance( ).seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine_instance( )( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine_instance( ) == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize( std::basic_ostream& ost ) { + ost << engine_instance( ); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize( std::basic_istream& ist ) { + ist >> engine_instance( ); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( engine_instance( ) ); + return IntegerDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( engine_instance( ) ); + return RealDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + static typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine_instance( ) ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + static typename std::enable_if< + details::has_reserve>::value + , Container>::type get(A from, A to, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + static typename std::enable_if< + !details::has_reserve>::value + , Container>::type get(A start, A end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + static typename std::enable_if< + !details::has_insert>::value + , Container>::type get(A start, A end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_insert>::value + , Container>::type get(A start, B end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( engine_instance( ) ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get( Dist& dist ) { + return dist( engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine_instance( ); + } + + /// return internal engine by ref + static Engine& engine() { + return engine_instance(); + } + protected: + /// get reference to the static engine instance + static Engine& engine_instance( ) { + static Engine engine{ Seeder{ }( ) }; + return engine; + } + }; + + /** + * \brief Base template class for random + * with thread_local API and thread_local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_thread_local { + public: + basic_random_thread_local( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine_instance( ).discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine_instance( ).seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + static void seed( Sseq& seq ) { + engine_instance( ).seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine_instance( )( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine_instance( ) == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + static void serialize( std::basic_ostream& ost ) { + ost << engine_instance( ); + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + static void deserialize( std::basic_istream& ist ) { + ist >> engine_instance( ); + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( engine_instance( ) ); + return IntegerDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( engine_instance( ) ); + return RealDist{ to, from }( engine_instance( ) ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + static typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + static typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(engine_instance())); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(engine_instance())); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + static typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine_instance( ) ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + static T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + static typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + static auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + static typename std::enable_if< + details::has_reserve>::value + , Container>::type get(A from, A to, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + static typename std::enable_if< + !details::has_reserve>::value + , Container>::type get(A start, A end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + static typename std::enable_if< + !details::has_insert>::value + , Container>::type get(A start, A end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + static typename std::enable_if< + std::is_same::value + && !details::has_insert>::value + , Container>::type get(A start, B end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + static T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( engine_instance( ) ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + static typename Dist::result_type get( Dist& dist ) { + return dist( engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine_instance( ) ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine_instance( ); + } + + /// return internal engine by ref + static Engine& engine() { + return engine_instance(); + } + protected: + /// get reference to the thread local engine instance + static Engine& engine_instance( ) { + thread_local Engine engine{ Seeder{ }( ) }; + return engine; + } + }; + + /** + * \brief Base template class for random + * with local API and local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template class IntegerDist = std::uniform_int_distribution, + template class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_local { + public: + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template + using integer_dist_t = IntegerDist; + + /// Type of used real distribution + template + using real_dist_t = RealDist; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + void discard( const unsigned long long z ) { + m_engine.discard( z ); + } + + /// Reseed by Seeder + void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + void seed( const typename Engine::result_type value = + Engine::default_seed ) { + m_engine.seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template + void seed( Sseq& seq ) { + m_engine.seed( seq ); + } + + /// return random number from engine in [min(), max()] range + typename Engine::result_type get( ) { + return m_engine( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + bool is_equal( const Engine& other ) { + return m_engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template + void serialize( std::basic_ostream& ost ) { + ost << m_engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template + void deserialize( std::basic_istream& ist ) { + ist >> m_engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist{ from, to }( m_engine ); + return IntegerDist{ to, from }( m_engine ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist{ from, to }( m_engine ); + return RealDist{ to, from }( m_engine ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get( T from = std::numeric_limits::min( ), + T to = std::numeric_limits::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional::value, + short, unsigned short>::type; + + return static_cast( get( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type + > + typename std::enable_if< + std::is_same::value + && details::is_supported_number::value + && details::is_supported_number::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed::value != std::is_unsigned::value + , C>::type get( A from = std::numeric_limits::min( ), + B to = std::numeric_limits::max( ) ) { + return get( static_cast( from ), static_cast( to ) ); + } + + /** + * \brief Generate a random character in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random character in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template + typename std::enable_if::value + , T>::type get(T from = std::numeric_limits::min(), + T to = std::numeric_limits::max()) { + if (from < to) // Allow range from higher to lower + return static_cast(IntegerDist{ static_cast(from), static_cast(to) }(m_engine)); + return static_cast(IntegerDist{ static_cast(to), static_cast(from) }(m_engine)); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template + typename std::enable_if::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( m_engine ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template + T get( std::initializer_list init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template + typename std::enable_if::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0 == size ) return last; + using diff_t = typename std::iterator_traits::difference_type; + return std::next( first, get( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template + auto get( Container& container ) -> + typename std::enable_if::value + , decltype(std::begin(container)) + >::type { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template class Container, typename A> + typename std::enable_if< + details::has_reserve>::value + , Container>::type get(A from, A to, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(from, to)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + * \note Container "reserve" method will be called before generation + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if< + std::is_same::value + && details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + container.reserve(size); + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, typename A> + typename std::enable_if< + !details::has_reserve>::value + , Container>::type get(A start, A end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if< + std::is_same::value + && !details::has_reserve>::value + , Container>::type get(A start, B end, std::size_t size) { + Container container; + + for (std::size_t i = 0; i < size; ++i) + container.insert(std::end(container), get(start, end)); + + return container; + } + + /** + * \brief Return array-like container filled with random values + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param N The number of elements in resulting container + * \return Container filled with random values + */ + template class Container, std::size_t N, typename A> + typename std::enable_if< + !details::has_insert>::value + , Container>::type get(A start, A end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return array-like container filled with random common_type values + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \param size The number of elements in resulting container + * \return Container filled with random values + */ + template< + template class Container, + std::size_t N, + typename Key, + typename A, + typename B, + typename C = typename std::common_type::type> + typename std::enable_if< + std::is_same::value + && !details::has_insert>::value + , Container>::type get(A start, B end) { + Container container = {{ 0 }}; + + for (std::size_t i = 0; i < N; ++i) + container[i] = get(start, end); + + return container; + } + + /** + * \brief Return random pointer from built-in array + * \param array The built-in array with elements + * \return Pointer to random element in array + */ + template + T* get( T( &array )[ N ] ) { + return std::addressof( array[ get( 0, N - 1 ) ] ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template + typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward( args )... }( m_engine ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template + typename Dist::result_type get( Dist& dist ) { + return dist( m_engine ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template + void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, m_engine ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template + void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + Engine get_engine( ) const { + return m_engine; + } + + /// return internal engine by ref + Engine& engine() { + return m_engine; + } + protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine( ) { + // Make seeder instance for seed return by reference like std::seed_seq + return Engine{ Seeder{ }( ) }; + } + protected: + /// The random number engine + Engine m_engine{ make_seeded_engine( ) }; + }; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with static storage + * \note Not thread safe but more prefomance + */ + using random_static = basic_random_static; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with thread_local storage + * \note Thread safe but less perfomance + */ + using random_thread_local = basic_random_thread_local; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses non static methods API and data with auto storage + * \note Not thread safe. Should construct on the stack at local scope + */ + using random_local = basic_random_local; } // namespace effolkronium From 67c3cf2da50a1a69d7c0d41f15e3560bad2cd679 Mon Sep 17 00:00:00 2001 From: ThomasDuvinage Date: Fri, 12 Sep 2025 13:47:50 +0900 Subject: [PATCH 62/83] feat(CLI11): use fetch content --- CMakeModules/FindCLI11.cmake | 10 + include/mesh_sampling/CLI11.hpp | 13891 ------------------------------ 2 files changed, 10 insertions(+), 13891 deletions(-) create mode 100644 CMakeModules/FindCLI11.cmake delete mode 100644 include/mesh_sampling/CLI11.hpp diff --git a/CMakeModules/FindCLI11.cmake b/CMakeModules/FindCLI11.cmake new file mode 100644 index 0000000..58039f6 --- /dev/null +++ b/CMakeModules/FindCLI11.cmake @@ -0,0 +1,10 @@ +# FetchCLI11.cmake +include(FetchContent) + +FetchContent_Declare( + cli11_proj + QUIET + GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git + GIT_TAG v2.5.0) + +FetchContent_MakeAvailable(cli11_proj) diff --git a/include/mesh_sampling/CLI11.hpp b/include/mesh_sampling/CLI11.hpp deleted file mode 100644 index 34017a7..0000000 --- a/include/mesh_sampling/CLI11.hpp +++ /dev/null @@ -1,13891 +0,0 @@ -// CLI11: Version 2.5.0 -// 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.5.0 -// -// CLI11 2.5.0 Copyright (c) 2017-2025 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 5 -#define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "2.5.0" - -// 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 - -/** 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 - -#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 - 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 -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 & ltrim(std::string & str); - -/// Trim anything from left of string -CLI11_INLINE std::string & ltrim(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) -{ - static const std::string badChars(std::string("\n") + '\0'); - return (str.find_first_of(badChars) == std::string::npos); -} - -/// check if a string is a container segment separator (empty or "%%") -inline bool is_separator(const std::string & str) -{ - static const std::string sep("%%"); - return (str.empty() || str == sep); -} - -/// 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; -} - -/// 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); - -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 = '\''); - -/// 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 & ltrim(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 & ltrim(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('\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 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); -} - -static const std::string escapedChars("\b\t\n\f\r\"\\"); -static const std::string escapedCharsCode("btnfr\"\\"); -static const std::string bracketChars{"\"'`[(<{"}; -static 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: - case std::string::npos: - 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) -{ - // 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) - { - 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) -{ - if(str.size() <= 1) - { - return false; - } - if(detail::is_binary_escaped_string(str)) - { - str = detail::extract_binary_string(str); - 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); - } - handle_secondary_array(str); - return true; - } - if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) - { - detail::remove_outer(str, str.front()); - handle_secondary_array(str); - return true; - } - return false; -} - -std::string get_environment_value(const std::string & env_name) -{ - char * buffer = nullptr; - std::string ename_string; - -#ifdef _MSC_VER - // Windows version - 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 - 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(word.length() + charsWritten > paragraphWidth) - { - out << '\n' << linePrefix; - charsWritten = 0; - } - - 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 -{ - 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::rjoin(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::rjoin(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 -{ -}; - -/// An instance to use in EnableIf -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, - 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>::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>::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< - T, - typename std::enable_if::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 : int -{ - 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(input.find_first_of("_'") != std::string::npos) - { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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 and trailing spaces - if(input.find_first_of("_'") != std::string::npos) - { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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; - } - return integral_conversion(input, output); -} - -/// 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(input.find_first_of("_'") != std::string::npos) - { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), 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::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::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 & current, 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 & current, 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 & current, 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 & current, 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 & current, 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 & current, 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.length() == 0) - { - 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 - { - std::ifstream input{name}; - 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 & sectionRef() - { - return configSection; - } - /// get the section - CLI11_NODISCARD const std::string & section() const - { - return configSection; - } - /// specify a particular section of the configuration file to use - ConfigBase * section(const std::string & sectionName) - { - 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); -}; - -/// Class wrapping some of the accessors of Validator -class CustomValidator : public Validator -{ -public: -}; -// 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 -{ - nonexistent, - file, - directory -}; - -/// get the type of the path from a file name -CLI11_INLINE path_type check_path(const char * file) noexcept; - -/// 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(); -}; - -/// Validate the given string is a legal ipv4 address -class IPV4Validator : public Validator -{ -public: - IPV4Validator(); -}; - -class EscapedStringTransformer : public Validator -{ -public: - EscapedStringTransformer(); -}; - -} // namespace detail - -// Static is not needed here, because global const implies static. - -/// Check for existing file (returns error message if check fails) -const detail::ExistingFileValidator ExistingFile; - -/// Check for an existing directory (returns error message if check fails) -const detail::ExistingDirectoryValidator ExistingDirectory; - -/// Check for an existing path -const detail::ExistingPathValidator ExistingPath; - -/// Check for an non-existing path -const detail::NonexistentPathValidator NonexistentPath; - -/// Check for an IP4 address -const detail::IPV4Validator ValidIPV4; - -/// convert escaped characters into their associated values -const detail::EscapedStringTransformer EscapedString; - -/// 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"); - -/// 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 -const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); - -/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number -const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); - -/// 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) - { - } -}; - -namespace detail -{ -template::type>::value, detail::enabler> = detail::dummy> -auto smart_deref(T value) -> decltype(*value) -{ - return *value; -} - -template::type>::value, detail::enabler> = detail::dummy> -typename std::remove_reference::type & smart_deref(T & value) -{ - 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}; -} - -// 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; -} - -} // 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::to_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 - { - 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); -}; - -namespace detail -{ -/// 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 IPV4Validator::IPV4Validator() : Validator("IPV4") -{ - func_ = [](std::string & ip_addr) - { - 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{}; - }; -} - -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{}; - }; -} - -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 -{ - -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 -/// @} - -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 -{ - 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 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}; - - /// @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" label - 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 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; - } - - ///@} - /// @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_; - } - - ///@} -}; - -/// 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; - -using Option_p = std::unique_ptr