diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000000..c3b2ac84ba0 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,2 @@ +src/Headless/CLI/CLI11.hpp +tests/unittest/tempdir.hpp diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 00000000000..cf09c3ff43f --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,6 @@ +tools: + cppcheck: + enabled: true + config: + suppress: + - missingIncludeSystem diff --git a/.github/workflows/build-matrix.yml b/.github/workflows/build-matrix.yml index 60cb1870212..d1d384a818e 100644 --- a/.github/workflows/build-matrix.yml +++ b/.github/workflows/build-matrix.yml @@ -36,6 +36,10 @@ on: required: false type: string default: "true" + linux-clang: + required: false + type: string + default: "true" build-release: required: false type: string @@ -54,8 +58,8 @@ on: default: "false" env: - install_prefix: install - build_prefix: build + install_prefix: ../install + build_prefix: ../build src_prefix: src jobs: @@ -74,7 +78,7 @@ jobs: [[ "true" == "${{ inputs.windows }}" && "true" == "${{inputs.linux}}" ]] && echo ',' [[ "true" == "${{ inputs.linux }}" ]] && echo '{ "name": "Ubuntu gcc", "os": "ubuntu-latest", "cc": "gcc", "cxx": "g++", "icon": "Linux", "extra-flags": "-DCMAKE_CXX_FLAGS=-Wno-deprecated-declarations -DRADIUM_ENABLE_GL_TESTING=ON" }' [[ "true" == "${{ inputs.windows }}" || "true" == "${{inputs.linux}}" ]] && [[ "true" == "${{inputs.macos}}" ]] && echo ',' - [[ "true" == "${{ inputs.macos }}" ]] && echo '{ "name": "MacOS clang", "os": "macos-13", "cc": "clang", "cxx": "clang++", "icon": "Apple", "extra-flags": "-DCMAKE_CXX_FLAGS=-Wno-deprecated-declarations" }' + [[ "true" == "${{ inputs.macos }}" ]] && echo '{ "name": "MacOS clang", "os": "macos-15", "cc": "clang", "cxx": "clang++", "icon": "Apple", "extra-flags": "-DCMAKE_CXX_FLAGS=-Wno-deprecated-declarations" }' echo '],' echo '"build-type" : [' [[ "true" == "${{ inputs.build-debug }}" ]] && echo '"Debug"' @@ -189,7 +193,6 @@ jobs: - name: Prepare directories run: | - mkdir -p "${{ env.src_prefix }}" mkdir -p "${{ steps.paths.outputs.radium_build_dir }}" mkdir -p "${{ steps.paths.outputs.external_build_dir }}" mkdir -p "${{ steps.paths.outputs.radium_install_dir }}" @@ -198,33 +201,45 @@ jobs: - name: Checkout remote head uses: actions/checkout@master with: - path: ${{ env.src_prefix }}/Radium-Engine submodules: 'recursive' - name: pull updated repo (e.g. with new VERSION) if: ${{ github.event_name == 'push' }} run: | - git -C "${{ env.src_prefix }}/Radium-Engine" pull origin ${{ github.event.ref }} - git -C "${{ env.src_prefix }}/Radium-Engine" submodule update --init --recursive + git pull origin ${{ github.event.ref }} + git submodule update --init --recursive - name: Cache externals id: cache-external uses: actions/cache@v4 with: - path: ${{ steps.paths.outputs.external_install_dir }} - key: ${{ matrix.config.name }}-${{ matrix.build-type }}-${{ matrix.qtversion.name }}-external-v1-${{ hashFiles('src/Radium-Engine/external/**/CMakeLists.txt') }} # no env in hashFiles + # could not cache relative path with .. so copy to externals inside workspace + path: externals + key: ${{ matrix.config.name }}-${{ matrix.build-type }}-${{ matrix.qtversion.name }}-external-v2-${{ hashFiles('external/**/CMakeLists.txt') }} # no env in hashFiles + + - name: Restore externals from cache + if: steps.cache-external.outputs.cache-hit == 'true' + run: | + # remove dir if any + rm -rf "${{ steps.paths.outputs.external_install_dir }}" + # copy cache to external_install_dir + cp -r externals "${{ steps.paths.outputs.external_install_dir }}" - name: Configure and build external if: steps.cache-external.outputs.cache-hit != 'true' run: | - "${{ env.src_prefix }}/Radium-Engine/scripts/build.sh" -e true -r false \ + "scripts/build.sh" -e true -r false \ -B "${{ env.build_prefix }}" -c ${{ matrix.build-type }} \ - -G Ninja --cc ${{ matrix.config.cc }} --cxx ${{ matrix.config.cxx }} \ + -G Ninja --cc "${{ matrix.config.cc }}" --cxx "${{ matrix.config.cxx }}" \ --install-external "${{ steps.paths.outputs.external_install_dir }}" + # remove cache dir if any + rm -rf externals + # copy external to cache location + cp -r "${{ steps.paths.outputs.external_install_dir }}" externals - name: Configure and build Radium run: | - "${{ env.src_prefix }}/Radium-Engine/scripts/build.sh" -e false -r true \ + "scripts/build.sh" -e false -r true \ -B "${{ env.build_prefix }}" -c ${{ matrix.build-type }} \ -G Ninja --cc ${{ matrix.config.cc }} --cxx ${{ matrix.config.cxx }} \ --install-external ${{ steps.paths.outputs.external_install_dir }} \ @@ -275,27 +290,15 @@ jobs: - name: Create coverage report if: ${{ inputs.coverage == 'true' }} run: | - cmake --build "${{ steps.paths.outputs.radium_build_dir }}" --config Debug --target fastcov_integration - cmake --build "${{ steps.paths.outputs.radium_build_dir }}" --config Debug --target fastcov_unittests + cmake --build "${{ steps.paths.outputs.radium_build_dir }}" --config Release --target coverage + head ${{ steps.paths.outputs.radium_build_dir }}/coverage/report.info - name: Upload unittests coverage report if: ${{ inputs.coverage == 'true' }} uses: codecov/codecov-action@v5 with: - fail_ci_if_error: true # optional (default = false) - files: ${{ steps.paths.outputs.radium_build_dir }}/fastcov_unittests.info - flags: unittests - token: ${{ secrets.CODECOV_TOKEN }} - verbose: true # optional (default = false) + # fail_ci_if_error: true # optional (default = false) disable_search: true - - - name: Upload integration coverage report - if: ${{ inputs.coverage == 'true' }} - uses: codecov/codecov-action@v5 - with: - fail_ci_if_error: true # optional (default = false) - files: ${{ steps.paths.outputs.radium_build_dir }}/fastcov_integration.info - flags: integration + files: ${{ steps.paths.outputs.radium_build_dir }}/coverage/report.info token: ${{ secrets.CODECOV_TOKEN }} - verbose: true # optional (default = false) - disable_search: true + verbose: false diff --git a/.github/workflows/pull-request-ci.yml b/.github/workflows/pull-request-ci.yml index 05fd5f7ed2d..e45d634474e 100644 --- a/.github/workflows/pull-request-ci.yml +++ b/.github/workflows/pull-request-ci.yml @@ -27,7 +27,8 @@ jobs: with: windows: "false" macos: "false" - build-release: "false" + build-release: "true" + build-debug: "false" coverage: "true" secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/push-rc-ci.yml b/.github/workflows/push-rc-ci.yml index c2b57402dec..3d0f9d613b4 100644 --- a/.github/workflows/push-rc-ci.yml +++ b/.github/workflows/push-rc-ci.yml @@ -37,7 +37,9 @@ jobs: with: windows: "false" macos: "false" - build-release: "false" + linux: "false" + build-release: "true" + build-debug: "false" coverage: "true" secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 526164750a5..ef812769f5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ option( "Enable examples app build. To install examples, build explicitly the target Install_RadiumExamples." OFF ) -option(RADIUM_ENABLE_COVERAGE "Enable coverage, gcc only. Experimental, need ENABLE_TESTING" OFF) +option(RADIUM_ENABLE_COVERAGE "Enable coverage, llvm only. sets ENABLE_TESTING ON" OFF) option(RADIUM_ENABLE_PCH "Enable precompiled headers." OFF) option(RADIUM_USE_DOUBLE "Use double precision for Scalar." OFF) option(RADIUM_GENERATE_LIB_CORE "Include Radium::Core in CMake project." ON) @@ -70,9 +70,6 @@ option( set(DISPLAY_WIDTH 80) -if(RADIUM_USE_DOUBLE) - add_definitions(-DCORE_USE_DOUBLE) -endif() # Changing the default value for CMAKE_BUILD_PARALLEL_LEVEL if(NOT DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL}) include(ProcessorCount) @@ -164,8 +161,9 @@ else() endif() if(RADIUM_ENABLE_COVERAGE) - set(RADIUM_ENABLE_TESTING "ON") + # include function and setup environment variable include(RadiumCoverage) + radium_setup_coverage_env() endif(RADIUM_ENABLE_COVERAGE) # ------------------------------------------------------------------------------ @@ -224,7 +222,8 @@ if(RADIUM_ENABLE_TESTING) endif() if(RADIUM_ENABLE_COVERAGE) - radium_setup_coverage_targets() + # add coverage flags to target and coverage specific target + radium_setup_coverage() endif() # Packaging stuff (deb, rpm, windows installer) add_subdirectory(packaging) diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake deleted file mode 100644 index 0463ad36d4d..00000000000 --- a/cmake/CodeCoverage.cmake +++ /dev/null @@ -1,770 +0,0 @@ -# Copyright (c) 2012 - 2017, Lars Bilke -# All rights reserved. -# -# Redistribution and use in source and binary forms, 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. -# -# CHANGES: -# -# 2012-01-31, Lars Bilke -# - Enable Code Coverage -# -# 2013-09-17, Joakim Söderberg -# - Added support for Clang. -# - Some additional usage instructions. -# -# 2016-02-03, Lars Bilke -# - Refactored functions to use named parameters -# -# 2017-06-02, Lars Bilke -# - Merged with modified version from github.com/ufz/ogs -# -# 2019-05-06, Anatolii Kurotych -# - Remove unnecessary --coverage flag -# -# 2019-12-13, FeRD (Frank Dana) -# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor -# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. -# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY -# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list -# - Set lcov basedir with -b argument -# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be -# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) -# - Delete output dir, .info file on 'make clean' -# - Remove Python detection, since version mismatches will break gcovr -# - Minor cleanup (lowercase function names, update examples...) -# -# 2019-12-19, FeRD (Frank Dana) -# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets -# -# 2020-01-19, Bob Apthorpe -# - Added gfortran support -# -# 2020-02-17, FeRD (Frank Dana) -# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters -# in EXCLUDEs, and remove manual escaping from gcovr targets -# -# 2021-01-19, Robin Mueller -# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run -# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional -# flags to the gcovr command -# -# 2020-05-04, Mihchael Davis -# - Add -fprofile-abs-path to make gcno files contain absolute paths -# - Fix BASE_DIRECTORY not working when defined -# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines -# -# 2021-05-10, Martin Stump -# - Check if the generator is multi-config before warning about non-Debug builds -# -# 2022-02-22, Marko Wehle -# - Change gcovr output from -o for --xml and --html output respectively. -# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". -# -# 2022-09-28, Sebastian Mueller -# - fix append_coverage_compiler_flags_to_target to correctly add flags -# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) -# -# USAGE: -# -# 1. Copy this file into your cmake modules path. -# -# 2. Add the following line to your CMakeLists.txt (best inside an if-condition -# using a CMake option() to enable it just optionally): -# include(CodeCoverage) -# -# 3. Append necessary compiler flags for all supported source files: -# append_coverage_compiler_flags() -# Or for specific target: -# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) -# -# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og -# -# 4. If you need to exclude additional directories from the report, specify them -# using full paths in the COVERAGE_EXCLUDES variable before calling -# setup_target_for_coverage_*(). -# Example: -# set(COVERAGE_EXCLUDES -# '${PROJECT_SOURCE_DIR}/src/dir1/*' -# '/path/to/my/src/dir2/*') -# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). -# Example: -# setup_target_for_coverage_lcov( -# NAME coverage -# EXECUTABLE testrunner -# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") -# -# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set -# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) -# Example: -# set(COVERAGE_EXCLUDES "dir1/*") -# setup_target_for_coverage_gcovr_html( -# NAME coverage -# EXECUTABLE testrunner -# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" -# EXCLUDE "dir2/*") -# -# 5. Use the functions described below to create a custom make target which -# runs your test executable and produces a code coverage report. -# -# 6. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug .. -# make -# make my_coverage_target -# - -include(CMakeParseArguments) - -option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) - -# Check prereqs -find_program(GCOV_PATH gcov) -find_program(LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) -find_program(FASTCOV_PATH NAMES fastcov fastcov.py) -find_program(GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat) -find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) -find_program(CPPFILT_PATH NAMES c++filt) - -if(NOT GCOV_PATH) - message(FATAL_ERROR "gcov not found! Aborting...") -endif() # NOT GCOV_PATH - -# Check supported compiler (Clang, GNU and Flang) -get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) -foreach(LANG ${LANGUAGES}) - if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) - message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") - endif() - elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" AND NOT "${CMAKE_${LANG}_COMPILER_ID}" - MATCHES "(LLVM)?[Ff]lang" - ) - message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") - endif() -endforeach() - -set(COVERAGE_COMPILER_FLAGS "-g" "--coverage" CACHE INTERNAL "") -set(COVERAGE_C_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) -set(COVERAGE_CXX_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) - -if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") - include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) - if(${HAVE_cxx_fprofile_abs_path}) - list(APPEND COVERAGE_CXX_COMPILER_FLAGS "-fprofile-abs-path") - endif() -endif() -if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") - include(CheckCCompilerFlag) - check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) - if(${HAVE_c_fprofile_abs_path}) - list(APPEND COVERAGE_C_COMPILER_FLAGS "-fprofile-abs-path") - endif() -endif() - -set(CMAKE_Fortran_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the Fortran compiler during coverage builds." FORCE -) -set(CMAKE_CXX_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE -) -set(CMAKE_C_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C compiler during coverage builds." FORCE -) -set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "" - CACHE STRING "Flags used for linking binaries during coverage builds." FORCE -) -set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "" - CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE -) -mark_as_advanced( - CMAKE_Fortran_FLAGS_COVERAGE CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE - CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE -) - -get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) - message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") -endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) - -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") - link_libraries(gcov) -endif() - -# Defines a target for running and collection code coverage information Builds dependencies, runs -# the given executable and outputs reports. NOTE! The executable should always have a ZERO as exit -# code otherwise the coverage generation will not complete. -# -# setup_target_for_coverage_lcov( NAME testrunner_coverage # New target name -# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR DEPENDENCIES -# testrunner # Dependencies to build first BASE_DIRECTORY "../" # Base directory -# for report # (defaults to PROJECT_SOURCE_DIR) EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to -# exclude (can be relative # to BASE_DIRECTORY, with CMake 3.4+) NO_DEMANGLE # Don't demangle C++ -# symbols # even if c++filt is found ) -function(setup_target_for_coverage_lcov) - - set(options NO_DEMANGLE SONARQUBE) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() # NOT LCOV_PATH - - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() # NOT GENHTML_PATH - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(DEFINED Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (CMake 3.4+: Also compute absolute paths) - set(LCOV_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) - if(CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif() - list(APPEND LCOV_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES LCOV_EXCLUDES) - - # Conditional arguments - if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) - set(GENHTML_EXTRA_ARGS "--demangle-cpp") - endif() - - # Setting up commands which will be run to generate coverage data. Cleanup lcov - set(LCOV_CLEAN_CMD - ${LCOV_PATH} - ${Coverage_LCOV_ARGS} - --gcov-tool - ${GCOV_PATH} - --directory - ${CMAKE_BINARY_DIR} - -b - ${BASEDIR} - --zerocounters - ) - # Create baseline to make sure untouched files show up in the report - set(LCOV_BASELINE_CMD - ${LCOV_PATH} - ${Coverage_LCOV_ARGS} - --gcov-tool - ${GCOV_PATH} - -c - -i - -d - ${CMAKE_BINARY_DIR} - -b - ${BASEDIR} - -o - ${Coverage_NAME}.base - ) - # Run tests - set(LCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) - # Capturing lcov counters and generating report - set(LCOV_CAPTURE_CMD - ${LCOV_PATH} - ${Coverage_LCOV_ARGS} - --gcov-tool - ${GCOV_PATH} - --directory - ${CMAKE_BINARY_DIR} - -b - ${BASEDIR} - --capture - --output-file - ${Coverage_NAME}.capture - ) - # add baseline counters - set(LCOV_BASELINE_COUNT_CMD - ${LCOV_PATH} - ${Coverage_LCOV_ARGS} - --gcov-tool - ${GCOV_PATH} - -a - ${Coverage_NAME}.base - -a - ${Coverage_NAME}.capture - --output-file - ${Coverage_NAME}.total - ) - # filter collected data to final coverage report - set(LCOV_FILTER_CMD - ${LCOV_PATH} - ${Coverage_LCOV_ARGS} - --gcov-tool - ${GCOV_PATH} - --remove - ${Coverage_NAME}.total - ${LCOV_EXCLUDES} - --output-file - ${Coverage_NAME}.info - ) - # Generate HTML output - set(LCOV_GEN_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o - ${Coverage_NAME} ${Coverage_NAME}.info - ) - if(${Coverage_SONARQUBE}) - # Generate SonarQube output - set(GCOVR_XML_CMD - ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} - ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} - ) - set(GCOVR_XML_CMD_COMMAND COMMAND ${GCOVR_XML_CMD}) - set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) - set(GCOVR_XML_CMD_COMMENT - COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml." - ) - endif() - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Executed command report") - message(STATUS "Command to clean up lcov: ") - string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") - message("${LCOV_CLEAN_CMD_SPACED}") - - message(STATUS "Command to create baseline: ") - string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") - message("${LCOV_BASELINE_CMD_SPACED}") - - message(STATUS "Command to run the tests: ") - string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") - message("${LCOV_EXEC_TESTS_CMD_SPACED}") - - message(STATUS "Command to capture counters and generate report: ") - string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") - message("${LCOV_CAPTURE_CMD_SPACED}") - - message(STATUS "Command to add baseline counters: ") - string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") - message("${LCOV_BASELINE_COUNT_CMD_SPACED}") - - message(STATUS "Command to filter collected data: ") - string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") - message("${LCOV_FILTER_CMD_SPACED}") - - message(STATUS "Command to generate lcov HTML output: ") - string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") - message("${LCOV_GEN_HTML_CMD_SPACED}") - - if(${Coverage_SONARQUBE}) - message(STATUS "Command to generate SonarQube XML output: ") - string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") - message("${GCOVR_XML_CMD_SPACED}") - endif() - endif() - - # Setup target - add_custom_target( - ${Coverage_NAME} - COMMAND ${LCOV_CLEAN_CMD} - COMMAND ${LCOV_BASELINE_CMD} - COMMAND ${LCOV_EXEC_TESTS_CMD} - COMMAND ${LCOV_CAPTURE_CMD} - COMMAND ${LCOV_BASELINE_COUNT_CMD} - COMMAND ${LCOV_FILTER_CMD} - COMMAND ${LCOV_GEN_HTML_CMD} ${GCOVR_XML_CMD_COMMAND} - # Set output files as GENERATED (will be removed on 'make clean') - BYPRODUCTS ${Coverage_NAME}.base ${Coverage_NAME}.capture ${Coverage_NAME}.total - ${Coverage_NAME}.info ${GCOVR_XML_CMD_BYPRODUCTS} ${Coverage_NAME}/index.html - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT - "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) - - # Show where to find the lcov info report - add_custom_command( - TARGET ${Coverage_NAME} POST_BUILD COMMAND ; - COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." - ${GCOVR_XML_CMD_COMMENT} - ) - - # Show info where to find the report - add_custom_command( - TARGET ${Coverage_NAME} POST_BUILD COMMAND ; - COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." - ) - -endfunction() # setup_target_for_coverage_lcov - -# Defines a target for running and collection code coverage information Builds dependencies, runs -# the given executable and outputs reports. NOTE! The executable should always have a ZERO as exit -# code otherwise the coverage generation will not complete. -# -# setup_target_for_coverage_gcovr_xml( NAME ctest_coverage # New target name -# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR DEPENDENCIES -# executable_target # Dependencies to build first BASE_DIRECTORY "../" # -# Base directory for report # (defaults to PROJECT_SOURCE_DIR) EXCLUDE "src/dir1/*" "src/dir2/*" # -# Patterns to exclude (can be relative # to BASE_DIRECTORY, with CMake 3.4+) ) The user can set the -# variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the GCVOR command. -function(setup_target_for_coverage_gcovr_xml) - - set(options NONE) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT GCOVR_PATH) - message(FATAL_ERROR "gcovr not found! Aborting...") - endif() # NOT GCOVR_PATH - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(DEFINED Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (CMake 3.4+: Also compute absolute paths) - set(GCOVR_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) - if(CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif() - list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES GCOVR_EXCLUDES) - - # Combine excludes to several -e arguments - set(GCOVR_EXCLUDE_ARGS "") - foreach(EXCLUDE ${GCOVR_EXCLUDES}) - list(APPEND GCOVR_EXCLUDE_ARGS "-e") - list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") - endforeach() - - # Set up commands which will be run to generate coverage data Run tests - set(GCOVR_XML_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) - # Running gcovr - set(GCOVR_XML_CMD - ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} - ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} - ) - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Executed command report") - - message(STATUS "Command to run tests: ") - string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") - message("${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") - - message(STATUS "Command to generate gcovr XML coverage data: ") - string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") - message("${GCOVR_XML_CMD_SPACED}") - endif() - - add_custom_target( - ${Coverage_NAME} - COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} - COMMAND ${GCOVR_XML_CMD} - BYPRODUCTS ${Coverage_NAME}.xml - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Running gcovr to produce Cobertura code coverage report." - ) - - # Show info where to find the report - add_custom_command( - TARGET ${Coverage_NAME} POST_BUILD COMMAND ; - COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." - ) -endfunction() # setup_target_for_coverage_gcovr_xml - -# Defines a target for running and collection code coverage information Builds dependencies, runs -# the given executable and outputs reports. NOTE! The executable should always have a ZERO as exit -# code otherwise the coverage generation will not complete. -# -# setup_target_for_coverage_gcovr_html( NAME ctest_coverage # New target name -# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR DEPENDENCIES -# executable_target # Dependencies to build first BASE_DIRECTORY "../" # -# Base directory for report # (defaults to PROJECT_SOURCE_DIR) EXCLUDE "src/dir1/*" "src/dir2/*" # -# Patterns to exclude (can be relative # to BASE_DIRECTORY, with CMake 3.4+) ) The user can set the -# variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the GCVOR command. -function(setup_target_for_coverage_gcovr_html) - - set(options NONE) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT GCOVR_PATH) - message(FATAL_ERROR "gcovr not found! Aborting...") - endif() # NOT GCOVR_PATH - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(DEFINED Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (CMake 3.4+: Also compute absolute paths) - set(GCOVR_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) - if(CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif() - list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES GCOVR_EXCLUDES) - - # Combine excludes to several -e arguments - set(GCOVR_EXCLUDE_ARGS "") - foreach(EXCLUDE ${GCOVR_EXCLUDES}) - list(APPEND GCOVR_EXCLUDE_ARGS "-e") - list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") - endforeach() - - # Set up commands which will be run to generate coverage data Run tests - set(GCOVR_HTML_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) - # Create folder - set(GCOVR_HTML_FOLDER_CMD ${CMAKE_COMMAND} -E make_directory - ${PROJECT_BINARY_DIR}/${Coverage_NAME} - ) - # Running gcovr - set(GCOVR_HTML_CMD - ${GCOVR_PATH} - --html - ${Coverage_NAME}/index.html - --html-details - -r - ${BASEDIR} - ${GCOVR_ADDITIONAL_ARGS} - ${GCOVR_EXCLUDE_ARGS} - --object-directory=${PROJECT_BINARY_DIR} - ) - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Executed command report") - - message(STATUS "Command to run tests: ") - string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") - message("${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") - - message(STATUS "Command to create a folder: ") - string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") - message("${GCOVR_HTML_FOLDER_CMD_SPACED}") - - message(STATUS "Command to generate gcovr HTML coverage data: ") - string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") - message("${GCOVR_HTML_CMD_SPACED}") - endif() - - add_custom_target( - ${Coverage_NAME} - COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} - COMMAND ${GCOVR_HTML_FOLDER_CMD} - COMMAND ${GCOVR_HTML_CMD} - BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Running gcovr to produce HTML code coverage report." - ) - - # Show info where to find the report - add_custom_command( - TARGET ${Coverage_NAME} POST_BUILD COMMAND ; - COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." - ) - -endfunction() # setup_target_for_coverage_gcovr_html - -# Defines a target for running and collection code coverage information Builds dependencies, runs -# the given executable and outputs reports. NOTE! The executable should always have a ZERO as exit -# code otherwise the coverage generation will not complete. -# -# setup_target_for_coverage_fastcov( NAME testrunner_coverage # New target name -# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR DEPENDENCIES -# testrunner # Dependencies to build first BASE_DIRECTORY "../" # Base directory -# for report # (defaults to PROJECT_SOURCE_DIR) EXCLUDE "src/dir1/" "src/dir2/" # Patterns to -# exclude. NO_DEMANGLE # Don't demangle C++ symbols # even if -# c++filt is found SKIP_HTML # Don't create html report POST_CMD -# perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from -# file paths ) -function(setup_target_for_coverage_fastcov) - - set(options NO_DEMANGLE SKIP_HTML) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS - POST_CMD - ) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT FASTCOV_PATH) - message(FATAL_ERROR "fastcov not found! Aborting...") - endif() - - if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (Patterns, not paths, for fastcov) - set(FASTCOV_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) - list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) - - # Conditional arguments - if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) - set(GENHTML_EXTRA_ARGS "--demangle-cpp") - endif() - - # Set up commands which will be run to generate coverage data - set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) - - set(FASTCOV_CAPTURE_CMD - ${FASTCOV_PATH} - ${Coverage_FASTCOV_ARGS} - --gcov - ${GCOV_PATH} - --search-directory - ${CMAKE_BINARY_DIR} - --process-gcno - --output - ${Coverage_NAME}.json - --exclude - ${FASTCOV_EXCLUDES} - ) - - set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} -C ${Coverage_NAME}.json --lcov --output - ${Coverage_NAME}.info - ) - - if(Coverage_SKIP_HTML) - set(FASTCOV_HTML_CMD ";") - else() - set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o - ${Coverage_NAME} ${Coverage_NAME}.info - ) - endif() - - set(FASTCOV_POST_CMD ";") - if(Coverage_POST_CMD) - set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) - endif() - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") - - message(" Running tests:") - string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") - message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") - - message(" Capturing fastcov counters and generating report:") - string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") - message(" ${FASTCOV_CAPTURE_CMD_SPACED}") - - message(" Converting fastcov .json to lcov .info:") - string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") - message(" ${FASTCOV_CONVERT_CMD_SPACED}") - - if(NOT Coverage_SKIP_HTML) - message(" Generating HTML report: ") - string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") - message(" ${FASTCOV_HTML_CMD_SPACED}") - endif() - if(Coverage_POST_CMD) - message(" Running post command: ") - string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") - message(" ${FASTCOV_POST_CMD_SPACED}") - endif() - endif() - - # Setup target - add_custom_target( - ${Coverage_NAME} - # Cleanup fastcov - COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} --search-directory - ${CMAKE_BINARY_DIR} --zerocounters - COMMAND ${FASTCOV_EXEC_TESTS_CMD} - COMMAND ${FASTCOV_CAPTURE_CMD} - COMMAND ${FASTCOV_CONVERT_CMD} - COMMAND ${FASTCOV_HTML_CMD} - COMMAND ${FASTCOV_POST_CMD} - # Set output files as GENERATED (will be removed on 'make clean') - BYPRODUCTS ${Coverage_NAME}.info ${Coverage_NAME}.json - ${Coverage_NAME}/index.html # report directory - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT - "Resetting code coverage counters to zero. Processing code coverage counters and generating report." - ) - - set(INFO_MSG - "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json." - ) - if(NOT Coverage_SKIP_HTML) - string( - APPEND - INFO_MSG - " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report." - ) - endif() - # Show where to find the fastcov info report - add_custom_command( - TARGET ${Coverage_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} - ) - -endfunction() # setup_target_for_coverage_fastcov - -function(append_coverage_compiler_flags) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") -endfunction() # append_coverage_compiler_flags - -# Setup coverage for specific library -function(append_coverage_compiler_flags_to_target name) - target_compile_options( - ${name} PRIVATE $<$:${COVERAGE_C_COMPILER_FLAGS}> - $<$:${COVERAGE_CXX_COMPILER_FLAGS}> - ) - target_link_options(${name} PRIVATE ${COVERAGE_COMPILER_FLAGS}) - if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" - OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU" - ) - target_link_libraries(${name} PRIVATE gcov) - endif() - -endfunction() diff --git a/cmake/ExternalInclude.cmake b/cmake/ExternalInclude.cmake index 4761c305778..3b911ce327f 100644 --- a/cmake/ExternalInclude.cmake +++ b/cmake/ExternalInclude.cmake @@ -13,6 +13,7 @@ set(RADIUM_EXTERNAL_CMAKE_OPTIONS -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_OBJECT_PATH_MAX=${CMAKE_OBJECT_PATH_MAX} -DCMAKE_MACOSX_RPATH=TRUE + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 --no-warn-unused-cli ) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") diff --git a/cmake/RadiumCoverage.cmake b/cmake/RadiumCoverage.cmake index eac45f1cfff..7a290c8154f 100644 --- a/cmake/RadiumCoverage.cmake +++ b/cmake/RadiumCoverage.cmake @@ -1,135 +1,205 @@ -if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES - "(Apple)?[Cc]lang" -) - set(LLVM_COVERAGE ON) -else() - set(GCOV_COVERAGE ON) -endif() - -if(DEFINED LLVM_COVERAGE) - find_program(LLVM_PROFDATA_PATH llvm-profdata) - find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LLVM_COV_PATH llvm-cov) +find_program(FASTCOV_PATH fastcov) +find_program(GENHTML_PATH genhtml) + +include(ListTargets) + +# need coverage_run target to be defined +function(radium_coverage_add_flags COMPILE_FLAGS LINK_FLAGS) + # prevent flags on catch2 and examples, add to specific targets + get_all_targets(TARGETS) + foreach(TARGET ${TARGETS}) + message(STATUS "add coverage flags to ${TARGET}") + target_compile_options(${TARGET} PUBLIC ${COMPILE_FLAGS}) + target_link_options(${TARGET} PUBLIC ${LINK_FLAGS}) + add_dependencies(coverage_run ${TARGET}) + endforeach() +endfunction() + +set(COVERAGE_DIR ${CMAKE_BINARY_DIR}/coverage/) +set(REPORT_DIR ${COVERAGE_DIR}/report) +set(PROF_DIR ${COVERAGE_DIR}/profiling) +set(COVERAGE_FILE ${COVERAGE_DIR}/report.info) + +function(radium_setup_coverage_gcc) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting.") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting.") + endif() + + make_directory(${REPORT_DIR}) + + set(COVERAGE_DELETE_REPORT_CMD rm -rf ${REPORT_DIR}) + set(COVERAGE_FASTCOV_ZEROCOUNTERS ${FASTCOV_PATH} --zerocounters) + set(COVERAGE_RUN_CMD ${CMAKE_CTEST_COMMAND} -T Test -T Coverage -j + $ENV{CMAKE_BUILD_PARALLEL_LEVEL} + ) + set(COVERAGE_FASTCOV_CMD + ${FASTCOV_PATH} + --exclude + /usr/include + /usr/lib + external + autogen + catch2 + Qt + --lcov + -o + ${COVERAGE_FILE} + ) + set(COVERAGE_REPORT_CMD ${GENHTML_PATH} --ignore-errors inconsistent --demangle-cpp -o + "${REPORT_DIR}" ${COVERAGE_FILE} + ) + + add_custom_target(coverage_delete_report COMMAND ${COVERAGE_DELETE_REPORT_CMD}) + add_custom_target(coverage_fastcov_zerocounters COMMAND ${COVERAGE_FASTCOV_ZEROCOUNTERS}) + add_custom_target(coverage_run COMMAND ${COVERAGE_RUN_CMD}) + + # after add target covreage_run + radium_coverage_add_flags("-O0;-g;--coverage;-fprofile-arcs;-ftest-coverage" "--coverage") + + add_custom_target(coverage_fastcov COMMAND ${COVERAGE_FASTCOV_CMD}) + add_custom_target(coverage_report COMMAND ${COVERAGE_REPORT_CMD}) + add_custom_target( + coverage + COMMAND ${COVERAGE_DELETE_REPORT_CMD} + COMMAND ${COVERAGE_FASTCOV_ZEROCOUNTERS_CMD} + COMMAND ${COVERAGE_RUN_CMD} + COMMAND ${COVERAGE_FASTCOV_CMD} + COMMAND ${COVERAGE_REPORT_CMD} + COMMENT "Running full coverage pipeline" + USES_TERMINAL + ) + +endfunction() + +function(radium_setup_coverage_llvm) + if(NOT LLVM_COV_PATH) message(FATAL_ERROR "llvm-cov not found! Aborting.") endif() if(NOT LLVM_PROFDATA_PATH) message(FATAL_ERROR "llvm-profdata not found! Aborting.") endif() - set(PROF_DIR ${CMAKE_BINARY_DIR}/llvm-cov/prof) - set(REPORT_DIR ${CMAKE_BINARY_DIR}/llvm-cov/report) - set(TEST_ENV_VAR "LLVM_PROFILE_FILE=${PROF_DIR}/coverage-%m-%p.profraw") - set(TEST_ENV_VAR ${TEST_ENV_VAR} PARENT_SCOPE) -else() - include(CodeCoverage) -endif() - -function(radium_setup_coverage_targets) - if(DEFINED LLVM_COVERAGE) - add_custom_target( - llvm_coverage - COMMAND find ${PROF_DIR} -name \"*.profraw\" -delete - COMMAND rm -rf ${REPORT_DIR} - COMMAND ${CMAKE_CTEST_COMMAND} -L unittests - COMMAND ${LLVM_PROFDATA_PATH} merge -sparse -o ${PROF_DIR}/unittests.profdata `find - ${PROF_DIR} -name \"*.profraw\"` - ) - endif() - # prevent flags on catch2 and examples, add to specific targets - include(ListTargets) + + make_directory(${REPORT_DIR}) + make_directory(${PROF_DIR}) + + set(COVERAGE_DELETE_CMD + find + ${PROF_DIR} + -name + \"*.profraw\" + -delete + -or + -name + \"*.profdata\" + -delete + ) + set(COVERAGE_DELETE_REPORT_CMD rm -rf ${REPORT_DIR}) + set(COVERAGE_RUN_CMD ${CMAKE_CTEST_COMMAND} -j $ENV{CMAKE_BUILD_PARALLEL_LEVEL}) + set(COVERAGE_MERGE_CMD + ${LLVM_PROFDATA_PATH} + merge + -sparse + -o + ${PROF_DIR}/coverage.profdata + `find + ${PROF_DIR} + -name + \"*.profraw\"` + ) + set(COVERAGE_REPORT_CMD + ${LLVM_COV_PATH} + show + $,${CMAKE_BINARY_DIR}> + ${LLVM_OBJS} + -instr-profile=${PROF_DIR}/coverage.profdata + -format=html + -output-dir=${REPORT_DIR} + -ignore-filename-regex="catch2" + -show-line-counts-or-regions + -show-directory-coverage + -Xdemangler + c++filt + -Xdemangler + -n + -sources + ${CMAKE_CURRENT_SOURCE_DIR} + ) + set(COVERAGE_EXPORT_CMD + ${LLVM_COV_PATH} + export + $,${CMAKE_BINARY_DIR}> # ${LLVM_OBJS} + -instr-profile=${PROF_DIR}/coverage.profdata + -ignore-filename-regex="catch2" + -sources + ${CMAKE_CURRENT_SOURCE_DIR} + -format=lcov + > + ${COVERAGE_FILE} + ) + + add_custom_target(coverage_run COMMAND ${COVERAGE_RUN_CMD}) + radium_coverage_add_flags( + "-fprofile-instr-generate;-fcoverage-mapping;-O0;-g" "-fprofile-instr-generate" LLVM_OBJS + ) + + add_custom_target(coverage_delete COMMAND ${COVERAGE_DELETE_CMD}) + + add_custom_target(coverage_delete_report COMMAND ${COVERAGE_DELETE_REPORT_CMD}) + add_custom_target(coverage_merge COMMAND ${COVERAGE_MERGE_CMD}) + get_all_targets(TARGETS) foreach(TARGET ${TARGETS}) - if(NOT ${TARGET} MATCHES ".*stream.*") - message(STATUS "add coverage flags to ${TARGET}") - if(DEFINED LLVM_COVERAGE) - target_compile_options( - ${TARGET} PRIVATE -fprofile-instr-generate -fcoverage-mapping - ) - target_link_options(${TARGET} PRIVATE -fprofile-instr-generate -fcoverage-mapping) - if(NOT ${TARGET} MATCHES "unittests") - list(APPEND LLVM_OBJS "--object" - "$,${CMAKE_BINARY_DIR}>" - ) - endif() - - add_dependencies(llvm_coverage ${TARGET}) - else() - append_coverage_compiler_flags_to_target(${TARGET}) - endif() - endif() + list(APPEND ${OUTPUT} "-object" + "$,${CMAKE_BINARY_DIR}>" + ) endforeach() - # - if(DEFINED LLVM_COVERAGE) - set(LLVM_COV - ${LLVM_COV_PATH} - show - $ - ${LLVM_OBJS} - -instr-profile=${PROF_DIR}/unittests.profdata - -format=html - -output-dir=${REPORT_DIR} - -show-line-counts-or-regions - -show-directory-coverage - -Xdemangler - c++filt - -Xdemangler - -n - ${CMAKE_CURRENT_SOURCE_DIR} + + add_custom_target(coverage_report COMMAND ${COVERAGE_REPORT_CMD}) + add_custom_target(coverage_export COMMAND ${COVERAGE_EXPORT_CMD}) + + add_custom_target( + coverage + COMMAND ${COVERAGE_DELETE_CMD} + COMMAND ${COVERAGE_DELETE_REPORT_CMD} + COMMAND ${COVERAGE_RUN_CMD} + COMMAND ${COVERAGE_MERGE_CMD} + COMMAND ${COVERAGE_REPORT_CMD} + COMMAND ${COVERAGE_EXPORT_CMD} + COMMENT "Running full coverage pipeline" + USES_TERMINAL + ) + +endfunction() + +function(radium_setup_coverage_env) + if((CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES + "(Apple)?[Cc]lang") + ) + set(TEST_ENV_VAR "LLVM_PROFILE_FILE=${PROF_DIR}/unittests/coverage-%m-%p.profraw" + PARENT_SCOPE ) - add_custom_command(TARGET llvm_coverage POST_BUILD COMMAND ${LLVM_COV}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # NOOP + else() + # NOOP + endif() +endfunction() + +function(radium_setup_coverage) + if((CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES + "(Apple)?[Cc]lang") + ) + radium_setup_coverage_llvm() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + radium_setup_coverage_gcc() else() - # fix error on github's lcov - foreach(LABEL unittests integration) - setup_target_for_coverage_lcov( - NAME - lcov_${LABEL} - EXECUTABLE - ${CMAKE_CTEST_COMMAND} - -L - ${LABEL} - --output-on-failure - -C - ${CMAKE_BUILD_TYPE} - -j - $ENV{CMAKE_BUILD_PARALLEL_LEVEL} - BASE_DIRECTORY - "/" - DEPENDENCIES - RadiumLibs - ${LABEL} - EXCLUDE - "${CMAKE_BINARY_DIR}/_deps/*" - "${CMAKE_BINARY_DIR}/*_autogen/*" - "/*external*/" - "/usr/*" - ) - - # Fastcov is not supported with gcov llvm: disabling for MacOS Source: - # https://github.com/RPGillespie6/fastcov/issues/36 - if(UNIX AND NOT APPLE AND FASTCOV_PATH) - setup_target_for_coverage_fastcov( - NAME - fastcov_${LABEL} - EXECUTABLE - ${CMAKE_CTEST_COMMAND} - -L - ${LABEL} - --output-on-failure - -C - ${CMAKE_BUILD_TYPE} - -j - $ENV{CMAKE_BUILD_PARALLEL_LEVEL} - DEPENDENCIES - RadiumLibs - ${LABEL} - BASE_DIRECTORY - "/" - EXCLUDE - "_deps" - "_autogen" - "external" - "/usr" - ) - endif() - endforeach() + message(FATAL_ERROR "coverage not supported with ${CMAKE_C_COMPILER_ID}") endif() endfunction() diff --git a/cmake/RadiumSetupFunctions.cmake b/cmake/RadiumSetupFunctions.cmake index 39e807b228d..0812d0954a3 100644 --- a/cmake/RadiumSetupFunctions.cmake +++ b/cmake/RadiumSetupFunctions.cmake @@ -8,6 +8,10 @@ cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +if(POLICY CMP0177) + cmake_policy(SET CMP0177 NEW) # install destination path are normalized +endif() + if(MSVC OR MSVC_IDE OR MINGW) include(${CMAKE_CURRENT_LIST_DIR}/Windeployqt.cmake) if(MSVC OR MSVC_IDE) @@ -906,20 +910,20 @@ function(configure_radium_package) endif() configure_package_config_file( - ${ARGS_PACKAGE_CONFIG} "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME}.cmake" + ${ARGS_PACKAGE_CONFIG} "${CMAKE_BINARY_DIR}/src/${CONFIG_FILE_NAME}.cmake" INSTALL_DESTINATION ${ARGS_PACKAGE_DIR} ) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME}.cmake" + install(FILES "${CMAKE_BINARY_DIR}/src/${CONFIG_FILE_NAME}.cmake" DESTINATION ${ARGS_PACKAGE_DIR} ) if(ARGS_PACKAGE_VERSION) write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME}Version.cmake" + "${CMAKE_BINARY_DIR}/src/${CONFIG_FILE_NAME}Version.cmake" VERSION ${ARGS_PACKAGE_VERSION} COMPATIBILITY SameMajorVersion ) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME}Version.cmake" + install(FILES "${CMAKE_BINARY_DIR}/src/${CONFIG_FILE_NAME}Version.cmake" DESTINATION ${ARGS_PACKAGE_DIR} ) endif() @@ -989,6 +993,24 @@ function(configure_radium_library) add_library(${ARGS_NAMESPACE}::${ARGS_TARGET} ALIAS ${ARGS_TARGET}) + # ------------------------------------------------------------------------- + # check if core has the seme use_double configuration has current build + get_target_property(_props Radium::Core INTERFACE_COMPILE_DEFINITIONS) + set(_core_use_double OFF) + if("CORE_USE_DOUBLE" IN_LIST _props) + set(_core_use_double ON) + endif() + + if((RADIUM_USE_DOUBLE AND NOT ${_core_use_double}) OR (NOT RADIUM_USE_DOUBLE + AND ${_core_use_double}) + ) + message( + FATAL_ERROR + "Mismatch RADIUM_USE_DOUBLE, ${ARS_TARGET} -> ${RADIUM_USE_DOUBLE}. Radium::Core -> ${_core_use_double}." + ) + endif() + # ------------------------------------------------------------------------- + install( TARGETS ${ARGS_TARGET} EXPORT ${ARGS_TARGET}Targets @@ -1002,15 +1024,12 @@ function(configure_radium_library) ) # export for build tree export(TARGETS ${ARGS_TARGET} NAMESPACE ${ARGS_NAMESPACE}:: - FILE "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_TARGET}Targets.cmake" + FILE "${CMAKE_BINARY_DIR}/src/${ARGS_TARGET}Targets.cmake" ) # export for the installation tree if(NOT ARGS_PACKAGE_DIR) set(ARGS_PACKAGE_DIR lib/cmake/Radium) endif() - if(ARGS_COMPONENT) - set(ARGS_PACKAGE_DIR ${ARGS_PACKAGE_DIR}/${ARGS_TARGET_DIR}) - endif() install(EXPORT ${ARGS_TARGET}Targets FILE ${ARGS_TARGET}Targets.cmake NAMESPACE ${ARGS_NAMESPACE}:: DESTINATION ${ARGS_PACKAGE_DIR} @@ -1127,6 +1146,9 @@ macro(configure_radium_target target) target_link_libraries(${target} PRIVATE PUBLIC ${RA_DEFAULT_LIBRARIES} INTERFACE) target_compile_definitions(${target} PRIVATE PUBLIC ${RA_DEFAULT_COMPILE_DEFINITIONS} INTERFACE) + if(RADIUM_USE_DOUBLE) + target_compile_definitions(${target} PUBLIC CORE_USE_DOUBLE) + endif() target_compile_options(${target} PRIVATE PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS} INTERFACE) diff --git a/cmake/Windeployqt.cmake b/cmake/Windeployqt.cmake index 34df7c222e1..20674329aa8 100644 --- a/cmake/Windeployqt.cmake +++ b/cmake/Windeployqt.cmake @@ -21,23 +21,23 @@ # SOFTWARE. include(${CMAKE_CURRENT_LIST_DIR}/QtFunctions.cmake) -find_qt_package(COMPONENTS Core REQUIRED) - -# Retrieve the absolute path to qmake and then use that path to find the windeployqt binary -get_target_property(_qmake_executable Qt::qmake IMPORTED_LOCATION) -get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) -find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}") - -# Running this with MSVC 2015 requires CMake 3.16+ -if((MSVC_VERSION VERSION_EQUAL 1900 OR MSVC_VERSION VERSION_GREATER 1900) AND CMAKE_VERSION - VERSION_LESS "3.16" -) - message(WARNING "Deploying with MSVC 2015+ requires CMake 3.16+") -endif() # Add commands that copy the Qt runtime to the target's output directory after build and install the # Qt runtime to the specified directory function(windeployqt target directory) + find_qt_package(COMPONENTS Core REQUIRED) + + # Retrieve the absolute path to qmake and then use that path to find the windeployqt binary + get_target_property(_qmake_executable Qt::qmake IMPORTED_LOCATION) + get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}") + + # Running this with MSVC 2015 requires CMake 3.16+ + if((MSVC_VERSION VERSION_EQUAL 1900 OR MSVC_VERSION VERSION_GREATER 1900) + AND CMAKE_VERSION VERSION_LESS "3.16" + ) + message(WARNING "Deploying with MSVC 2015+ requires CMake 3.16+") + endif() set(QT_OPTIONS "") diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 038a5548a25..82ca28d0cb8 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -25,8 +25,10 @@ else() endif() message_setting(CAN_BUILD_DOC) if(CAN_BUILD_DOC) + if(TARGET Doxygen::dot) + get_target_property(DOT_IMPORTED Doxygen::dot "IMPORTED") + endif() # Search for plantUML for creating UML diagrams from doxygen - get_target_property(DOT_IMPORTED Doxygen::dot "IMPORTED") message_setting("DOT_IMPORTED") find_file(PLANT_UML_PATH NAMES plantuml.jar plantuml PATH_SUFFIXES PlantUML plantuml Plantuml PATHS /usr/share /usr/local/share/ /usr/local/bin /opt/local/bin c/Program\ Files* diff --git a/doc/developer/cmakeutilities.md b/doc/developer/cmakeutilities.md index 2180be78192..b2265966d4e 100644 --- a/doc/developer/cmakeutilities.md +++ b/doc/developer/cmakeutilities.md @@ -532,37 +532,16 @@ This file provides components informations needed by `find_package(Radium COMPON Here is a simple example of `Config.cmake.in` for the NewComponent example. ~~~{.cmake} -# Check if the component is requested for import and is not already imported -if(NewComponent_FOUND and NOT TARGET NewComponent) - # This helper variable will be ON if the component targets should be imported - set(Configure_NewComponent ON) - # check for dependency on other components - if(NOT Core_FOUND) # The dependency on Core was not explicitely requested by the user - # Include the configuration script fot the dependency if it exists - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake") - set(Core_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake) - else() - # If the dependency is not found, generates an error with a clear error message - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::NewComponent : dependency Core not found") - set(Configure_NewComponent OFF) - endif() - endif() - # .. Do this for all dependencies -endif() +# first add Radium component which NewComponent depends. +add_component_dependency(Core) # this is a macro defined in src/Config.cmake.in -# Configure the component if needed -if(Configure_NewComponent) - # manage the dependency on external libraries (e.g. to the ExternalDependency package) - find_dependency(ExternalDependency REQUIRED) - # Specific configuration for windows - if(MSVC OR MSVC_IDE OR MINGW) - add_imported_dir(FROM ExternalDependency::Target TO RadiumExternalDlls_location) - endif() - # define the imported targets for the NewComponent - include("${CMAKE_CURRENT_LIST_DIR}/NewComponentTargets.cmake") +if(NOT TARGET Radium::NewComponent) + # setup component dependencies if needed + # include componentTagrets + include("${CMAKE_CURRENT_LIST_DIR}/NewComponent.cmake") endif() + +check_target_set_found(NewComponent) # Also a macro ~~~ You can refer to the configuration of the `Core`, `Engine`, `Gui`, `PluginBase` and `IO`components for more practical diff --git a/examples/CoreExample/main.cpp b/examples/CoreExample/main.cpp index db47667ef60..3039fc250ab 100644 --- a/examples/CoreExample/main.cpp +++ b/examples/CoreExample/main.cpp @@ -6,9 +6,6 @@ #include #include #include -#include -#include -#include int main( int /*argc*/, char** /*argv*/ ) { using namespace Ra::Core; diff --git a/examples/ParameterEditing/main.cpp b/examples/ParameterEditing/main.cpp index 739985f696f..13f05c420b1 100644 --- a/examples/ParameterEditing/main.cpp +++ b/examples/ParameterEditing/main.cpp @@ -1,5 +1,6 @@ // Include Radium base application and its simple Gui +#include "Core/Containers/MakeShared.hpp" #include #include #include @@ -13,18 +14,38 @@ #include #include -#include +#include using namespace Ra::Engine::Data; using namespace Ra::Gui; using namespace Ra::Core; +// Enumeration that will be available as string or numeric value +// four cases +// enum class with enum converter +enum class Values1 : unsigned int { VALUE1_0 = 10, VALUE1_1 = 20, VALUE1_2 = 30 }; +using ValuesType1 = typename std::underlying_type_t; + +// classic enum, added as int in set -> int spin box +enum Values2 : unsigned int { VALUE2_0 = 1, VALUE2_1 = 2, VALUE2_2 = 3 }; +using Values2Type2 = typename std::underlying_type_t; + +// classic enum, added as int in set with converter -> enum combo box +enum Values3 : unsigned int { VALUE3_0 = 100, VALUE3_1 = 200, VALUE3_2 = 300 }; +using ValuesType3 = typename std::underlying_type_t; + +// enum class without converter, but with values in constraints -> enum combo box +enum class Values4 : unsigned int { VALUE4_0 = 0, VALUE4_1 = 1 }; +using ValuesType4 = typename std::underlying_type_t; + +// enum class without converter nor values are ignored. + // Visitor to print all or some of the parameters stored in a RenderParameter object. // This visitor is very similar to the one used to build the editing ui (RenderParameterUiBuilder // in ParameterSetEditor.cpp) and could be used with a predicate function accepting variable wrt // their name struct ParameterPrinter { - using types = Ra::Engine::Data::RenderParameters::BindableTypes; + using types = Ra::Engine::Data::RenderParameters::BindableTypes::Append; using SelectionPredicate = std::function; void operator()( @@ -64,6 +85,17 @@ struct ParameterPrinter { } } + template , bool> = true> + void operator()( + const std::string& name, + T& value, + SelectionPredicate&& pred = []( const std::string& ) { return true; } ) { + if ( pred( name ) ) { + std::cout << name << " (" << Utils::demangleType() << ") --> " + << static_cast>( value ) << "\n"; + } + } + void operator()( const std::string& name, Ra::Core::Utils::Color& value, @@ -111,13 +143,6 @@ struct ParameterPrinter { } }; -// Enumeration that will be available as string or numeric value -enum Values : unsigned int { VALUE_0 = 10, VALUE_1 = 20, VALUE_2 = 30 }; -using ValuesType = typename std::underlying_type_t; - -enum Values2 : unsigned int { VALUE2_0 = 10, VALUE2_1 = 20, VALUE2_2 = 30 }; -using Values2Type = typename std::underlying_type_t; - int main( int argc, char* argv[] ) { //! [Filling json parameter descriptor] auto parameterSet_metadata = R"( @@ -127,10 +152,23 @@ int main( int argc, char* argv[] ) { "editable": true, "type": "boolean" }, - "enum": { - "description": "unscoped enum, ranging from 10 to 30 with step 10", + "enum1": { + "description": "enum class with converter", + "type": "enum" + }, + "enum2": { + "description": "plain enum without converter, added as uint", "type": "enum" }, + "enum3": { + "description": "plain enum with converter, added as uint", + "type": "enum" + }, + "enum4": { + "description": "enum class without converter, values are mendatory", + "type": "enum", + "values":["VALUE4_0","VALUE4_1"] + }, "int_constrained": { "description": "Integer between -10 and 10", "minimum": -10, @@ -186,19 +224,28 @@ int main( int argc, char* argv[] ) { //! [Creating the editing dialog] //! [Management of string<->value conversion for enumeration parameters] - auto vnc = new Ra::Core::Utils::EnumConverter( { { Values::VALUE_0, "VALUE_0" }, - { Values::VALUE_1, "VALUE_1" }, - { Values::VALUE_2, "VALUE_2" } } ); - auto valuesEnumConverter = std::shared_ptr>( vnc ); + using namespace Ra::Core::Utils; + auto valuesEnumConverter1 = std::make_shared>( + EnumConverter::InitializerList { { Values1::VALUE1_0, "VALUE1_0" }, + { Values1::VALUE1_1, "VALUE1_1" }, + { Values1::VALUE1_2, "VALUE1_2" } } ); + + auto valuesEnumConverter3 = std::make_shared>( + EnumConverter::InitializerList { { Values3::VALUE3_0, "VALUE3_0" }, + { Values3::VALUE3_1, "VALUE3_1" }, + { Values3::VALUE3_2, "VALUE3_2" } } ); //! [Management of string<->value conversion for enumeration parameters] //! [filling the parameter set to edit ] RenderParameters parameters; using namespace Ra::Core::VariableSetEnumManagement; - addEnumConverter( parameters, "enum", valuesEnumConverter ); parameters.setVariable( "bool", false ); - setEnumVariable( parameters, "enum", Values::VALUE_1 ); + addEnumConverter( parameters, "enum1", valuesEnumConverter1 ); + addEnumConverter( parameters, "enum3", valuesEnumConverter3 ); + parameters.setVariable( "enum1", Values1::VALUE1_1 ); setEnumVariable( parameters, "enum2", Values2::VALUE2_1 ); + setEnumVariable( parameters, "enum3", Values3::VALUE3_2 ); + parameters.setVariable( "enum4", Values4::VALUE4_0 ); parameters.setVariable( "int", int( 0 ) ); parameters.setVariable( "int_constrained", int( 0 ) ); parameters.setVariable( "uint", (unsigned int)( 10 ) ); @@ -236,6 +283,12 @@ int main( int argc, char* argv[] ) { // std::is_assignable_v builder.addOperator( builder ); + // add operator for enum1, functor already present as template accepting std::is_enum + builder.addOperator( builder ); + builder.addOperator( builder ); // do nothing, since enum2 added as int + builder.addOperator( builder ); // do nothing, since enum3 added as int + builder.addOperator( builder ); + parameters.visit( builder ); auto printParameter = [¶meters]( const std::string& p ) { @@ -253,6 +306,5 @@ int main( int argc, char* argv[] ) { //! [Printing several parameters after editing ] std::cout << "\nPrinting all parameters before quit : "; parameters.visit( ParameterPrinter {} ); - //! [Printing several parameters after editing ] } diff --git a/external/Dataflow/CMakeLists.txt b/external/Dataflow/CMakeLists.txt index b956ad91415..5f7a1090c41 100644 --- a/external/Dataflow/CMakeLists.txt +++ b/external/Dataflow/CMakeLists.txt @@ -21,12 +21,12 @@ if(NOT DEFINED QtNodes_DIR) ExternalProject_Add( QtNodes GIT_REPOSITORY https://github.com/paceholder/nodeeditor.git - GIT_TAG 34b6f81 + GIT_TAG f84b5d8 GIT_SHALLOW TRUE GIT_PROGRESS TRUE INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX= - "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" + -DBUILD_DOCS=OFF "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" ) set_external_dir(QtNodes "lib/cmake/QtNodes/") add_dependencies(DataflowExternals QtNodes) diff --git a/external/IO/CMakeLists.txt b/external/IO/CMakeLists.txt index a965d83aefb..d55b707c579 100644 --- a/external/IO/CMakeLists.txt +++ b/external/IO/CMakeLists.txt @@ -29,11 +29,11 @@ if(RADIUM_IO_ASSIMP) ExternalProject_Add( assimp GIT_REPOSITORY https://github.com/assimp/assimp.git - GIT_TAG tags/v5.0.1 + GIT_TAG tags/v6.0.2 GIT_SHALLOW TRUE GIT_PROGRESS TRUE - PATCH_COMMAND git reset --hard && git apply -v --ignore-whitespace - "${CMAKE_CURRENT_LIST_DIR}/patches/assimp.patch" + # PATCH_COMMAND git reset --hard && git apply -v --ignore-whitespace + # "${CMAKE_CURRENT_LIST_DIR}/patches/assimp.patch" INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} -DASSIMP_BUILD_ASSIMP_TOOLS=False @@ -44,7 +44,7 @@ if(RADIUM_IO_ASSIMP) "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" ) - set_external_dir(assimp "lib/cmake/assimp-5.0/") + set_external_dir(assimp "lib/cmake/assimp-6.0/") add_dependencies(IOExternals assimp) else() diff --git a/scripts/build.sh b/scripts/build.sh index 8063af3049a..8eb39b0b6de 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -8,13 +8,15 @@ COMMAND_COLOR='\033[0;90m' NC='\033[0m' - if [ "$(uname)" == "Darwin" ]; then JOBS=$(sysctl -n hw.logicalcpu) else JOBS=$(nproc) fi +# Get SDK directory (directory where this script is located) +THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}/" )/../" && pwd )" + BUILD_TYPE=Release BUILD_EXT=true BUILD_RADIUM=true @@ -26,6 +28,8 @@ ENABLE_EXAMPLE=ON ENABLE_COVERAGE=OFF ENABLE_TESTING=ON +BUILD_ROOT="${THIS_DIR}/build" + VERBOSE=true function eval_verbose { if [ "$VERBOSE" = true ]; then @@ -52,7 +56,7 @@ function usage { echo " -o, --options OPTS More cmake configure options." echo " double quote if multiple OPTS, single quote if spaces" echo " as in -o \"-Da -Ddir='a b'\"" - echo " -B, --build-prefix DIR Set BUILD_PREFIX (Radium-Engine/build)," + echo " -B, --build-prefix DIR Set BUILD_PREFIX (default: $BUILD_ROOT)," echo " then uses BUILD_PREFIX/CONFIG/{external|Radium-Engine}" echo " -i, --install DIR Install path PREFIX" echo " (default: ../radium-install)" @@ -165,13 +169,7 @@ while [[ $# -gt 0 ]]; do esac done - -# Get SDK directory (directory where this script is located) -THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}/" )/../" && pwd )" - -if [ -z ${BUILD_PREFIX+x} ]; then - BUILD_ROOT="${THIS_DIR}/build" -else +if [ ! -z ${BUILD_PREFIX+x} ]; then BUILD_ROOT="${BUILD_PREFIX}" fi @@ -196,12 +194,13 @@ if [ ! -z ${CMAKE_GENERATOR+x} ]; then GENERATOR_ARG="-G ${CMAKE_GENERATOR}" fi -COMPILER_ARG="" +COMPILER_ARG1="" if [ ! -z ${CC+x} ]; then - COMPILER_ARG="-DCMAKE_C_COMPILER=${CC}" + COMPILER_ARG1="-DCMAKE_C_COMPILER=${CC}" fi -if [ ! -z ${CCX+x} ]; then - COMPILER_ARG="${COMPILER_ARG} -DCMAKE_C_COMPILER=${CXX}" +COMPILER_ARG2="" +if [ ! -z ${CXX+x} ]; then + COMPILER_ARG2="-DCMAKE_CXX_COMPILER=${CXX}" fi if [ "${BUILD_EXT}" = true ]; then @@ -209,7 +208,8 @@ if [ "${BUILD_EXT}" = true ]; then eval_verbose cmake -S "${THIS_DIR}/external" -B "${EXTERNAL_BUILD_DIR}" \ ${GENERATOR_ARG:+"${GENERATOR_ARG}"} \ - ${COMPILER_ARG:+"${COMPILER_ARG}"} \ + ${COMPILER_ARG1:+"${COMPILER_ARG1}"} \ + ${COMPILER_ARG2:+"${COMPILER_ARG2}"} \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ -DCMAKE_EXECUTE_PROCESS_COMMAND_ECHO=NONE \ -DCMAKE_INSTALL_MESSAGE=LAZY \ @@ -229,7 +229,8 @@ if [ "${BUILD_RADIUM}" = true ]; then eval_verbose cmake -S "${THIS_DIR}/" -B "${RADIUM_BUILD_DIR}" \ -DCMAKE_EXECUTE_PROCESS_COMMAND_ECHO=NONE \ ${GENERATOR_ARG:+"${GENERATOR_ARG}"} \ - ${COMPILER_ARG:+"${COMPILER_ARG}"} \ + ${COMPILER_ARG1:+"${COMPILER_ARG1}"} \ + ${COMPILER_ARG2:+"${COMPILER_ARG2}"} \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ -DCMAKE_INSTALL_PREFIX="${RADIUM_INSTALL_DIR}" \ -C "${EXTERNAL_INSTALL_DIR}/radium-options.cmake" \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 50b71e8094a..665588009b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,7 +77,7 @@ file( "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/QtFunctions.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FindFilesystem.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/RPath.cmake" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + DESTINATION ${CMAKE_BINARY_DIR}/src ) if(MSVC OR MSVC_IDE OR MINGW) @@ -85,7 +85,7 @@ if(MSVC OR MSVC_IDE OR MINGW) DESTINATION ${CONFIG_PACKAGE_LOCATION} ) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Windeployqt.cmake" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + DESTINATION ${CMAKE_BINARY_DIR}/src ) endif() diff --git a/src/Config.cmake.in b/src/Config.cmake.in index 0a3af8e2588..7666db6505e 100644 --- a/src/Config.cmake.in +++ b/src/Config.cmake.in @@ -1,60 +1,112 @@ -cmake_policy(SET CMP0057 NEW) +cmake_minimum_required(VERSION 3.18) +cmake_policy(SET CMP0057 NEW) # Support for IN_LIST -# Add current list dir to CMAKE_MODULE_PATH to enable find package of installed modules -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) +# -------------------------------------------------------------------------------------------------- +# Project and module setup +# -------------------------------------------------------------------------------------------------- -#--------------------------- Base configuration for installation and rpath settings ---------------- +# Add current list directory to CMAKE_MODULE_PATH (so we can include local helper cmake files) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") # -------------------------------------------------------------------------------------------------- -# Load Radium rpath configuration -include(${CMAKE_CURRENT_LIST_DIR}/RPath.cmake) - +# Base configuration for installation and RPATH settings # -------------------------------------------------------------------------------------------------- -# Configure components +include("${CMAKE_CURRENT_LIST_DIR}/RPath.cmake") -# Load Radium cmake functions -include(${CMAKE_CURRENT_LIST_DIR}/RadiumSetupFunctions.cmake) +# -------------------------------------------------------------------------------------------------- +# Load helper functions +# -------------------------------------------------------------------------------------------------- +include("${CMAKE_CURRENT_LIST_DIR}/RadiumSetupFunctions.cmake") @PACKAGE_INIT@ -# manage components -set(Radium_supported_components @RADIUM_COMPONENTS@) -if(Radium_FIND_COMPONENTS) - # Components are requested, fetch them and their dependencies - foreach(component ${Radium_FIND_COMPONENTS}) - if(${component} IN_LIST Radium_supported_components) - set(${component}_FOUND True) - if(NOT TARGET ${component}) - include("${CMAKE_CURRENT_LIST_DIR}/${component}/Radium${component}Config.cmake") - endif() - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Unsupported component: ${component}") - endif() - endforeach() -else() - # No component is requested, fetch only Core - set(Core_FOUND True) - if(NOT TARGET Core) - include("${CMAKE_CURRENT_LIST_DIR}/Core/RadiumCoreConfig.cmake") + +# -------------------------------------------------------------------------------------------------- +# Helper macros +# -------------------------------------------------------------------------------------------------- +include(CMakeFindDependencyMacro) + +macro(add_component_dependency comp) + if(NOT ${comp} IN_LIST Radium_FIND_COMPONENTS) + list(APPEND Radium_FIND_COMPONENTS ${comp}) + set(Radium_FIND_REQUIRED_${comp} TRUE) + include("${CMAKE_CURRENT_LIST_DIR}/Radium${comp}Config.cmake") + endif() +endmacro() + +macro(check_target_set_found comp) + if(TARGET Radium::${comp}) + set(Radium_${comp}_FOUND TRUE) endif() +endmacro() + + +# -------------------------------------------------------------------------------------------------- +# Component management +# -------------------------------------------------------------------------------------------------- +set(Radium_SUPPORTED_COMPONENTS @RADIUM_COMPONENTS@) + +# Default component if none is specified +if (NOT Radium_FIND_COMPONENTS) + set(Radium_FIND_COMPONENTS Core) # default components endif() -if(NOT TARGET Core) - # Compute paths - get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) - # get up in the directories until we found a "Resources/Radium" subdir TODO : instead of - # 'Resources/Radium' that might not exists if Engine nor Gui are compiled, search for a file - # identifying Radium (a la Eigen) and that is installed at the root of the Radium Bundle - while(NOT EXISTS ${SELF_DIR}/Resources/Radium) - get_filename_component(SELF_DIR "${SELF_DIR}" DIRECTORY) +foreach(component IN LISTS Radium_FIND_COMPONENTS) + if(component IN_LIST Radium_SUPPORTED_COMPONENTS) + set(Radium_${component}_FOUND FALSE) + include("${CMAKE_CURRENT_LIST_DIR}/Radium${component}Config.cmake") + if(NOT TARGET Radium::${component}) + set(Radium_${component}_FOUND FALSE) + set(Radium_NOT_FOUND_MESSAGE "Radium component not found: '${component}'. " ) + endif() + else() + set(Radium_FOUND FALSE) + set(Radium_NOT_FOUND_MESSAGE + "Unsupported Radium component requested: '${component}'. " + "Available components: ${Radium_SUPPORTED_COMPONENTS}" + ) + return() + endif() +endforeach() + +# -------------------------------------------------------------------------------------------------- +# Root and resource directory setup +# -------------------------------------------------------------------------------------------------- +if(NOT TARGET Radium::Core) + # Compute installation root by going up until Resources/Radium exists + get_filename_component(_self_dir "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + set(_found_root FALSE) + + while(NOT "${_self_dir}" STREQUAL "/" AND NOT _found_root) + if(EXISTS "${_self_dir}/Resources/Radium") + set(RADIUM_ROOT_DIR "${_self_dir}") + set(_found_root TRUE) + else() + get_filename_component(_self_dir "${_self_dir}" DIRECTORY) + endif() endwhile() - set(RADIUM_CONFIG_DIR ${CMAKE_CURRENT_LIST_DIR}) - set(RADIUM_ROOT_DIR "${SELF_DIR}") + if(NOT _found_root) + message(WARNING "Could not locate Radium root directory (Resources/Radium not found).") + set(RADIUM_ROOT_DIR "NOT_FOUND") + endif() + + unset(_self_dir) + unset(_found_root) else() - set(RADIUM_CONFIG_DIR ${CMAKE_CURRENT_LIST_DIR}) + # Already configured as an imported target set(RADIUM_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}") endif() + +set(RADIUM_CONFIG_DIR "${CMAKE_CURRENT_LIST_DIR}") set(RADIUM_RESOURCES_DIR "${RADIUM_ROOT_DIR}/Resources/Radium") -set(RADIUM_PLUGINS_DIR "${RADIUM_ROOT_DIR}/Plugins") +set(RADIUM_PLUGINS_DIR "${RADIUM_ROOT_DIR}/Plugins") + +check_required_components(Radium) +# -------------------------------------------------------------------------------------------------- +# Developer feedback +# -------------------------------------------------------------------------------------------------- +#if(NOT RADIUM_ROOT_DIR STREQUAL "NOT_FOUND") +# message(STATUS "Radium configuration loaded from: ${RADIUM_CONFIG_DIR}") +# message(STATUS "Radium root directory: ${RADIUM_ROOT_DIR}") +#endif() diff --git a/src/Core/CMakeLists.txt b/src/Core/CMakeLists.txt index 093a764811f..e5862fa75a3 100644 --- a/src/Core/CMakeLists.txt +++ b/src/Core/CMakeLists.txt @@ -49,6 +49,10 @@ target_compile_definitions( -DCXX_FILESYSTEM_NAMESPACE=${CXX_FILESYSTEM_NAMESPACE} ) +if(RADIUM_USE_DOUBLE) + target_compile_definitions(${ra_core_target} PUBLIC CORE_USE_DOUBLE) +endif() + configure_file(radium_backtrace.h.in radium_backtrace.h) target_include_directories(${ra_core_target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) if(Backtrace_FOUND) diff --git a/src/Core/Config.cmake.in b/src/Core/Config.cmake.in index 5a4c84d8f68..51ae76b5578 100644 --- a/src/Core/Config.cmake.in +++ b/src/Core/Config.cmake.in @@ -1,26 +1,42 @@ # -------------- Configuration of the Radium Core targets and definitions ------------------------- -if(NOT TARGET Core) - include(CMakeFindDependencyMacro) + +if(NOT TARGET Radium::Core) find_dependency(Threads REQUIRED) if(@OPENMP_FOUND@) # Indicates if OpenMP has been used when compiling the Radium libraries find_dependency(OpenMP REQUIRED) endif() - set(glm_DIR "@glm_DIR@") - set(Eigen3_DIR "@Eigen3_DIR@") - set(OpenMesh_DIR "@OpenMesh_DIR@") - set(cpplocate_DIR "@cpplocate_DIR@") - set(nlohmann_json_DIR "@nlohmann_json_DIR@") - + if(NOT Eigen3_DIR) + set_and_check(Eigen3_DIR "@Eigen3_DIR@") + endif() + if(NOT OpenMesh_DIR) + set_and_check(OpenMesh_DIR "@OpenMesh_DIR@") + endif() + if(NOT cpplocate_DIR) + set_and_check(cpplocate_DIR "@cpplocate_DIR@") + endif() + if(NOT nlohmann_json_DIR) + set_and_check(nlohmann_json_DIR "@nlohmann_json_DIR@") + endif() set(RadiumExternalDlls_location "") find_dependency(Eigen3 REQUIRED NO_DEFAULT_PATH) - find_dependency(OpenMesh REQUIRED NO_DEFAULT_PATH) + find_dependency(OpenMesh REQUIRED COMPONENTS Core Tools NO_DEFAULT_PATH) find_dependency(cpplocate REQUIRED NO_DEFAULT_PATH) find_dependency(nlohmann_json REQUIRED NO_DEFAULT_PATH) find_dependency(Filesystem COMPONENTS Final Experimental REQUIRED) + if(MSVC OR MSVC_IDE OR MINGW) add_imported_dir(FROM cpplocate::cpplocate TO RadiumExternalDlls_location) endif() + include("${CMAKE_CURRENT_LIST_DIR}/CoreTargets.cmake") -endif(NOT TARGET Core) + + define_property( + TARGET PROPERTY RADIUM_USE_DOUBLE BRIEF_DOCS "Radium::Core use Double as Scalar." + FULL_DOCS "Radium::Core use Double as Scalar. OFF -> float, ON -> double" + ) + set_target_properties(Radium::Core PROPERTIES RADIUM_USE_DOUBLE @RADIUM_USE_DOUBLE@) +endif() + +check_target_set_found(Core) diff --git a/src/Core/Containers/Tex.hpp b/src/Core/Containers/Tex.hpp index c1e09f30cc3..4c4f74a1e41 100644 --- a/src/Core/Containers/Tex.hpp +++ b/src/Core/Containers/Tex.hpp @@ -58,10 +58,10 @@ template struct NLinearInterpolator { template static T interpolate( - const Grid& grid, // grid from which values are read - const typename Tex::Vector& fact, // factors of the interpolation (between 0 and 1) - const typename Tex::IdxVector& size, // size of the dual grid - const typename Tex::IdxVector& clamped_nearest ) // base indices of the cell + const Grid& /*grid*/, // grid from which values are read + const typename Tex::Vector& /*fact*/, // factors of the interpolation \in [0,1] + const typename Tex::IdxVector& /*size*/, // size of the dual grid + const typename Tex::IdxVector& /*clamped_nearest*/ ) // base indices of the cell { CORE_ERROR( "N-linear interpolation not implemented for N= " << N ); return T(); diff --git a/src/Core/Containers/VariableSet.hpp b/src/Core/Containers/VariableSet.hpp index e1367ffef1c..8ac9f6a9660 100644 --- a/src/Core/Containers/VariableSet.hpp +++ b/src/Core/Containers/VariableSet.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -545,7 +546,7 @@ auto VariableSet::insertVariable( const std::string& name, const T& value ) template auto VariableSet::getVariable( const std::string& name ) -> T& { - return const_cast( const_cast( this )->getVariable( name ) ); + return getVariableHandle( name )->second; } template diff --git a/src/Core/CoreMacros.hpp b/src/Core/CoreMacros.hpp index bf275a5f694..8fb577d6f4a 100644 --- a/src/Core/CoreMacros.hpp +++ b/src/Core/CoreMacros.hpp @@ -231,7 +231,7 @@ # define STRONG_INLINE inline # define NO_INLINE __attribute__((noinline)) -# define DLL_EXPORT +# define DLL_EXPORT __attribute__((visibility("default"))) # define DLL_IMPORT # define STDCALL __attribute__((stdcall)) diff --git a/src/Core/Geometry/DistanceQueries.hpp b/src/Core/Geometry/DistanceQueries.hpp index 50ef18d596a..4a0b1964310 100644 --- a/src/Core/Geometry/DistanceQueries.hpp +++ b/src/Core/Geometry/DistanceQueries.hpp @@ -173,7 +173,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Vector3& c ) { /* * This function projects the query point Q on the triangle plane to solve the - * planar problem described by the following schema of Voronoi zones : + * planar problem described by the following schema of Voronoi zones : * 3 / * -- C * |\\ 6 @@ -199,13 +199,13 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Vector3 ac = c - a; const Vector3 aq = q - a; - CORE_ASSERT( ab.cross( ac ).squaredNorm() > 0, "Triangle ABC is degenerate" ); + CORE_ASSERT( ab.cross( ac ).squaredNorm() > 0_ra, "Triangle ABC is degenerate" ); const Scalar d1 = ab.dot( aq ); const Scalar d2 = ac.dot( aq ); // Closest point is A. (zone 1) - const bool m1 = d1 <= 0 && d2 <= 0; + const bool m1 = d1 <= 0_ra && d2 <= 0_ra; if ( m1 ) { output.meshPoint = a; output.distanceSquared = aq.squaredNorm(); @@ -218,7 +218,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Scalar d4 = ac.dot( bq ); // Closest point is B (zone 2) - const bool m2 = d3 >= 0 && d4 <= d3; + const bool m2 = d3 >= 0_ra && d4 <= d3; if ( m2 ) { output.meshPoint = b; output.distanceSquared = bq.squaredNorm(); @@ -231,7 +231,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Scalar d6 = ac.dot( cq ); // Closest point is C (zone 3) - const bool m3 = ( d6 >= 0 && d5 <= d6 ); + const bool m3 = ( d6 >= 0_ra && d5 <= d6 ); if ( m3 ) { output.meshPoint = c; output.distanceSquared = cq.squaredNorm(); @@ -243,7 +243,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Scalar v1 = d1 / ( d1 - d3 ); // Closest point is on AB (zone 4) - const bool m4 = vc <= 0 && d1 >= 0 && d3 <= 0; + const bool m4 = vc <= 0_ra && d1 >= 0 && d3 <= 0_ra; if ( m4 ) { output.meshPoint = a + v1 * ab; output.distanceSquared = ( output.meshPoint - q ).squaredNorm(); @@ -255,7 +255,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Scalar w1 = d2 / ( d2 - d6 ); // Closest point is on AC (zone 5) - const bool m5 = vb <= 0 && d2 >= 0 && d6 <= 0; + const bool m5 = vb <= 0_ra && d2 >= 0_ra && d6 <= 0_ra; if ( m5 ) { output.meshPoint = a + w1 * ac; output.distanceSquared = ( output.meshPoint - q ).squaredNorm(); @@ -267,7 +267,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, const Scalar w2 = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); // Closest point is on BC (zone 6) - const bool m6 = va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0; + const bool m6 = va <= 0_ra && ( d4 - d3 ) >= 0_ra && ( d5 - d6 ) >= 0_ra; if ( m6 ) { output.meshPoint = b + w2 * ( c - b ); output.distanceSquared = ( output.meshPoint - q ).squaredNorm(); @@ -276,7 +276,7 @@ inline RA_CORE_API PointToTriangleOutput pointToTriSq( const Vector3& q, } // Closest point is on the triangle - const Scalar d = Scalar( 1 ) / ( va + vb + vc ); + const Scalar d = 1_ra / ( va + vb + vc ); const Scalar v2 = vb * d; const Scalar w3 = vc * d; diff --git a/src/Core/Utils/EnumConverter.hpp b/src/Core/Utils/EnumConverter.hpp index 25b49fd01bd..3bc271d26bc 100644 --- a/src/Core/Utils/EnumConverter.hpp +++ b/src/Core/Utils/EnumConverter.hpp @@ -29,8 +29,8 @@ class EnumConverter /// getEnumerator -> Enum /// After thinking, complexify management of variableset and visitor. public: - explicit EnumConverter( std::initializer_list> pairs ) : - m_valueToString { pairs } {} + using InitializerList = std::initializer_list>; + explicit EnumConverter( InitializerList pairs ) : m_valueToString { pairs } {} std::string getEnumerator( EnumBaseType v ) const { return m_valueToString( v ); } EnumBaseType getEnumerator( const std::string& v ) const { return m_valueToString.key( v ); } diff --git a/src/Core/Utils/StringUtils.cpp b/src/Core/Utils/StringUtils.cpp index 3457b0a6c98..0a72bfada4a 100644 --- a/src/Core/Utils/StringUtils.cpp +++ b/src/Core/Utils/StringUtils.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -74,6 +75,54 @@ std::size_t replaceAllInString( std::string& inout, std::string_view what, std:: std::size_t removeAllInString( std::string& inout, std::string_view what ) { return replaceAllInString( inout, what, "" ); } + +void remove_bracket_block( std::string& inout, const std::string& what, char open_sep ) { + replace_bracket_block( inout, what, "", open_sep ); +} + +void replace_bracket_block( std::string& inout, + const std::string& what, + const std::string& with, + char open_sep ) { + const std::map open_to_close = { + { '<', '>' }, + { '(', ')' }, + { '[', ']' }, + { '{', '}' }, + }; + + const auto it = open_to_close.find( open_sep ); + if ( it == open_to_close.end() ) throw std::invalid_argument( "Unsupported open separator" ); + + const char close_sep = it->second; + + std::string target = what + open_sep; + + size_t i = 0; + + for ( size_t pos = inout.find( target, i ); pos != std::string::npos; + pos = inout.find( target, i ) ) { + + size_t j = pos + target.size(); + int depth = 1; + while ( j < inout.size() && depth > 0 ) { + if ( inout[j] == open_sep ) + ++depth; + else if ( inout[j] == close_sep ) + --depth; + ++j; + } + // If unmatched open_sep, abort safely + if ( depth != 0 ) break; + + while ( j < inout.size() && std::isspace( static_cast( inout[j] ) ) ) { + ++j; + } + // Replace the full "what<...>" block with "with" + inout.replace( pos, j - pos, with ); + } +} + } // namespace Utils } // namespace Core } // namespace Ra diff --git a/src/Core/Utils/StringUtils.hpp b/src/Core/Utils/StringUtils.hpp index caad652620c..a72d4ce323e 100644 --- a/src/Core/Utils/StringUtils.hpp +++ b/src/Core/Utils/StringUtils.hpp @@ -48,6 +48,21 @@ replaceAllInString( std::string& inout, std::string_view what, std::string_view */ RA_CORE_API std::size_t removeAllInString( std::string& inout, std::string_view what ); +/** + * replace what open_sep xxx close_sep by with in inout + * e.g. what=allocator, open_sep='<'. Corresponding close_sep='>' ... + * skip any nested <> () ... + * no checks are done concerning the balancing, if not well balanced might remove all the ramaning. + * after remove occurs, "with" in inserted in the input. + */ +RA_CORE_API void replace_bracket_block( std::string& inout, + const std::string& what, + const std::string& with = "", + char open_sep = '<' ); + +RA_CORE_API void +remove_bracket_block( std::string& inout, const std::string& what, char open_sep = '<' ); + } // namespace Utils } // namespace Core } // namespace Ra diff --git a/src/Core/Utils/TypesUtils.cpp b/src/Core/Utils/TypesUtils.cpp index 30584b41e76..6cfd496aea3 100644 --- a/src/Core/Utils/TypesUtils.cpp +++ b/src/Core/Utils/TypesUtils.cpp @@ -1,7 +1,7 @@ #include #include #include -#include + #include #include @@ -11,7 +11,8 @@ namespace Utils { namespace TypeInternal { RA_CORE_API auto makeTypeReadable( const std::string& fullType ) -> std::string { - static std::map knownTypes { + // use vector to have ordered replacement. + static std::vector> knownTypes { { "std::", "" }, { "__cxx11::", "" }, #ifndef CORE_USE_DOUBLE @@ -39,13 +40,28 @@ RA_CORE_API auto makeTypeReadable( const std::string& fullType ) -> std::string { "Eigen::Matrix", "Vector4i" }, { "Eigen::Matrix", "Vector4ui" }, // Windows (visual studio 2022) specific name fix - { " __ptr64", "" } }; + { " __ptr64", "" }, + // std::string + { "basic_string, allocator>", "string" } }; - auto processedType = fullType; + auto result = fullType; for ( const auto& [key, value] : knownTypes ) { - Ra::Core::Utils::replaceAllInString( processedType, key, value ); + Ra::Core::Utils::replaceAllInString( result, key, value ); } - return processedType; + + // Common verbose STL template components to remove + remove_bracket_block( result, "allocator" ); + remove_bracket_block( result, "char_traits" ); + remove_bracket_block( result, "hash" ); + remove_bracket_block( result, "equal_to" ); + // redondant with above replaceAllInString + // replace_bracket_b-lock( result, "basic_string", "string" ); + + result = std::regex_replace( result, std::regex( R"(\s*,(\s*,)+)" ), "," ); + result = std::regex_replace( result, std::regex( R"(\s*,\s*>)" ), ">" ); + result = std::regex_replace( result, std::regex( R"(\s+)" ), " " ); + + return result; } } // namespace TypeInternal diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index 3c9f07fa02c..2e7b325b137 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -9,6 +9,7 @@ # include #endif +#include #include #include @@ -76,11 +77,15 @@ RA_CORE_API auto makeTypeReadable( const std::string& ) -> std::string; template auto simplifiedDemangledType() noexcept -> std::string { - static auto demangled_name = []() { - std::string demangledType = - TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); - return demangledType; - }(); + // don't get why we need a lambda here ? + // static auto demangled_name = []() { + // std::string demangledType = + // TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); + // return demangledType; + // }(); + static auto demangled_name = + TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); + return demangled_name; } @@ -156,26 +161,16 @@ struct TypeList { using Append = typename TypeListInternal::TSAppendImpl::type; }; -#ifdef _WIN32 -// On windows (since MSVC 2019), typeid( T ).name() (and then typeIndex.name() returns the demangled -// name inline std::string demangleType( const std::type_index& typeIndex ) noexcept { - std::string retval = typeIndex.name(); - removeAllInString( retval, "class " ); - removeAllInString( retval, "struct " ); - removeAllInString( retval, "__cdecl" ); - replaceAllInString( retval, "& __ptr64", "&" ); - replaceAllInString( retval, ",", ", " ); - replaceAllInString( retval, " >", ">" ); - replaceAllInString( retval, "__int64", "long" ); - replaceAllInString( retval, "const &", "const&" ); - return retval; -} -#else -// On Linux/macos, use the C++ ABI demangler -inline std::string demangleType( const std::type_index& typeIndex ) noexcept { - int error = 0; std::string retval; + + // On windows (since MSVC 2019), typeid( T ).name() (and then typeIndex.name() returns the + // demangled name +#ifdef _WIN32 + retval = typeIndex.name(); + // On Linux/macos, use the C++ ABI demangler +#else + int error = 0; char* name = abi::__cxa_demangle( typeIndex.name(), 0, 0, &error ); if ( error == 0 ) { retval = name; } else { @@ -185,11 +180,31 @@ inline std::string demangleType( const std::type_index& typeIndex ) noexcept { retval = std::string( "Type demangler error : " ) + std::to_string( error ); } std::free( name ); - removeAllInString( retval, "__1::" ); // or "::__1" ? +#endif + removeAllInString( retval, "class " ); + removeAllInString( retval, "struct " ); + removeAllInString( retval, "__cdecl" ); + replaceAllInString( retval, "& __ptr64", "&" ); + replaceAllInString( retval, ",", ", " ); replaceAllInString( retval, " >", ">" ); + replaceAllInString( retval, "__int64", "long" ); + replaceAllInString( retval, "const &", "const&" ); + removeAllInString( retval, "__cxx11::" ); // windows<>gnu inconsistency + removeAllInString( retval, "__1::" ); // or "::__1" ? + replaceAllInString( retval, " >", ">" ); + + // " , , , ,," -> , + retval = std::regex_replace( retval, std::regex( R"(\s*,(\s*,)+)" ), "," ); + //" ," -> , + retval = std::regex_replace( retval, std::regex( R"(\s*,)" ), "," ); + // " , >" -> > + retval = std::regex_replace( retval, std::regex( R"(\s*,\s*>)" ), ">" ); + // " " -> " " + retval = std::regex_replace( retval, std::regex( R"(\s+)" ), " " ); + return retval; } -#endif + template std::string demangleType() noexcept { // once per one type diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in index 562e08464e7..fab786fb93e 100644 --- a/src/Dataflow/Config.cmake.in +++ b/src/Dataflow/Config.cmake.in @@ -1,44 +1,12 @@ # -------------- Configuration of the Radium Dataflow targets and definitions ----------------------- # Setup Dataflow and check for dependencies -if (Dataflow_FOUND AND NOT TARGET Dataflow) - set(Configure_Dataflow ON) - # verify dependencies - if(NOT DataflowCore_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake") - set(DataflowCore_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowCore not found") - set(Configure_Dataflow OFF) - endif() - endif() - if(NOT DataflowQtGui_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowQtGui/RadiumDataflowQtGuiConfig.cmake") - set(DataflowQtGui_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../DataflowQtGui/RadiumDataflowQtGuiConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowQtGui not found") - set(Configure_Dataflow OFF) - endif() - endif() - -# to be uncommented when dataflow rendering subpackage will be available -# if(NOT DataflowRendering_FOUND) -# if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake") -# set(DataflowRendering_FOUND TRUE) -# include(${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake) -# else() -# set(Radium_FOUND False) -# set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowRendering not found") -# set(Configure_Dataflow OFF) -# endif() -# endif() -endif() +add_component_dependency(DataflowCore) +add_component_dependency(DataflowQtGui) # configure Dataflow component -if(Configure_Dataflow) +if(NOT TARGET Radium::Dataflow) include("${CMAKE_CURRENT_LIST_DIR}/DataflowTargets.cmake") endif() + +check_target_set_found(Dataflow) diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt index 1569584d308..9b7e21c63b1 100644 --- a/src/Dataflow/Core/CMakeLists.txt +++ b/src/Dataflow/Core/CMakeLists.txt @@ -31,7 +31,7 @@ configure_radium_library( # Generate cmake package configure_radium_package( NAME ${ra_dataflowcore_target} PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in - PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowcore_target}" NAME_PREFIX "Radium" + NAME_PREFIX "Radium" ) set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowcore_target} PARENT_SCOPE) diff --git a/src/Dataflow/Core/Config.cmake.in b/src/Dataflow/Core/Config.cmake.in index 200aa5af5f9..db24ffc067b 100644 --- a/src/Dataflow/Core/Config.cmake.in +++ b/src/Dataflow/Core/Config.cmake.in @@ -1,21 +1,11 @@ # -------------- Configuration of the Radium DataflowCore targets and definitions ----------------------- # Setup Engine and check for dependencies -if (DataflowCore_FOUND AND NOT TARGET DataflowCore) - set(Configure_DataflowCore ON) +add_component_dependency(Core) + +if (NOT TARGET Radium::DataflowCore) # verify dependencies - if(NOT Core_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake") - set(Core_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowCore: dependency Core not found") - set(Configure_DataflowCore OFF) - endif() - endif() + include("${CMAKE_CURRENT_LIST_DIR}/DataflowCoreTargets.cmake") endif() -if(Configure_DataflowCore) - include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/DataflowCoreTargets.cmake") -endif() +check_target_set_found(DataflowCore) diff --git a/src/Dataflow/Core/Core.cpp b/src/Dataflow/Core/Core.cpp index dbed2cb77cd..69311f4765e 100644 --- a/src/Dataflow/Core/Core.cpp +++ b/src/Dataflow/Core/Core.cpp @@ -35,6 +35,8 @@ using namespace Ra::Dataflow::Core; void CoreNodes__Initializer() { PortFactory::createInstance(); + NodeJsonDeserializer::createInstance(); + NodeJsonSerializer::createInstance(); using namespace Ra::Dataflow::Core::NodeFactoriesManager; if ( factory( factory_manager().default_factory_name() ) ) { return; } auto factory = create_factory( factory_manager().default_factory_name() ); diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 70179dc0c15..a1ff211f32c 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -1,8 +1,11 @@ +#include "Core/Tasks/Task.hpp" +#include "Core/Tasks/TaskQueue.hpp" #include #include +// #include // need tbb #include #include @@ -33,23 +36,79 @@ void DataflowGraph::init() { } } +bool DataflowGraph::execute2() { + if ( m_inputs.size() > 0 || m_outputs.size() > 0 ) return true; + + if ( !m_ready ) { + if ( !compile() ) { return false; } + } + + Ra::Core::TaskQueue queue( 10 ); + std::map node_to_id; + std::atomic_bool result { true }; + + std::mutex log_mutex; + + m_executed_node_count.store( 0 ); + + // fill task queue + for ( const auto& n : m_node_info_map ) { + // Transform the entity + auto n_ptr = n.first; + node_to_id[n_ptr] = queue.registerTask( std::make_unique( + [this, &result, n_ptr, &log_mutex]() { + bool executed = n_ptr->execute(); + m_executed_node_count++; + result = result.load() && executed; + + { + std::lock_guard lock( log_mutex ); + m_log_callback( m_executed_node_count.load(), m_active_node_count ); + } + }, + n_ptr->display_name() ) ); + } + + // fill dependencies + for ( const auto& n : m_node_info_map ) { + auto n_ptr = n.first; + for ( const auto& l : n.second.second ) { + queue.addDependency( node_to_id[n_ptr], node_to_id[l] ); + } + } + queue.startTasks(); + queue.waitForTasks(); + + return result; +} + bool DataflowGraph::execute() { + return execute2(); if ( m_inputs.size() > 0 || m_outputs.size() > 0 ) return true; if ( !m_ready ) { if ( !compile() ) { return false; } } - bool result = true; + + m_executed_node_count.store( 0 ); + std::atomic_bool result { true }; std::for_each( - m_nodes_by_level.begin(), m_nodes_by_level.end(), [&result]( const auto& level ) { - std::for_each( level.begin(), level.end(), [&result]( auto node ) { - bool executed = node->execute(); - if ( !executed ) { - LOG( logERROR ) << "Execution failed with node " << node->instance_name() - << " (" << node->model_name() << ")."; - } - result = result && executed; - } ); + m_nodes_by_level.begin(), m_nodes_by_level.end(), [this, &result]( const auto& level ) { + std::for_each( // std::execution::par, + level.begin(), + level.end(), + [this, &result]( auto node ) { + bool executed = node->execute(); + if ( !executed ) { + LOG( logERROR ) << "Execution failed with node " << node->instance_name() + << " (" << node->model_name() << ")."; + } + m_executed_node_count++; + std::cerr << "progress " << m_executed_node_count.load() << "/" + << m_active_node_count << "\n"; + + result = result.load() && executed; + } ); } ); return result; } @@ -268,7 +327,7 @@ bool DataflowGraph::add_node( std::shared_ptr newNode ) { bool DataflowGraph::remove_node( std::shared_ptr node ) { // This is to prevent graph destruction from the graph editor, depending on how it is used - if ( m_nodesAndLinksProtected ) { return false; } + if ( m_nodes_and_links_protected ) { return false; } if ( auto itr = std::find( m_nodes.begin(), m_nodes.end(), node ); itr != m_nodes.end() ) { m_nodes.erase( itr ); @@ -296,7 +355,7 @@ bool DataflowGraph::are_ports_compatible( const Node* nodeFrom, return true; } -void nodeNotFoundMessage( const std::string& type, const std::string& name, const Node* node ) { +void node_not_found_message( const std::string& type, const std::string& name, const Node* node ) { LOG( logERROR ) << "DataflowGraph::add_link Unable to find " << type << "input port " << name << " from destination node " << node->instance_name() << " (" << node->model_name() << ")"; @@ -310,12 +369,12 @@ bool DataflowGraph::add_link( const std::shared_ptr& nodeFrom, auto [inputIdx, inputPort] = nodeTo->input_by_name( nodeToInputName ); if ( !inputPort ) { - nodeNotFoundMessage( "input", nodeToInputName, nodeTo.get() ); + node_not_found_message( "input", nodeToInputName, nodeTo.get() ); return false; } auto [outputIdx, outputPort] = nodeFrom->output_by_name( nodeFromOutputName ); if ( !outputPort ) { - nodeNotFoundMessage( "output", nodeFromOutputName, nodeFrom.get() ); + node_not_found_message( "output", nodeFromOutputName, nodeFrom.get() ); return false; } @@ -386,7 +445,7 @@ bool DataflowGraph::remove_link( std::shared_ptr node, const std::string& bool DataflowGraph::remove_link( std::shared_ptr node, const PortIndex& in_port_index ) { // This is to prevent graph destruction from the graph editor, depending on how it is used - if ( m_nodesAndLinksProtected ) { return false; } + if ( m_nodes_and_links_protected ) { return false; } // Check node's existence in the graph bool ret = false; @@ -431,11 +490,11 @@ bool DataflowGraph::compile() { // Find useful nodes (directly or indirectly connected to a Sink) /// Node -> level, linked nodes - std::unordered_map>> infoNodes; + m_node_info_map.clear(); if ( m_output_node ) { - backtrack_graph( m_output_node.get(), infoNodes ); - infoNodes.emplace( m_output_node.get(), std::pair>( 0, {} ) ); + backtrack_graph( m_output_node.get(), m_node_info_map ); + m_node_info_map.emplace( m_output_node.get(), LevelAndLinked { 0, {} } ); } for ( auto const& n : m_nodes ) { // Find all active sinks, skip m_output_node @@ -445,9 +504,9 @@ bool DataflowGraph::compile() { return p->is_linked(); } ) ) { - infoNodes.emplace( n.get(), std::pair>( 0, {} ) ); + m_node_info_map.emplace( n.get(), LevelAndLinked { 0, {} } ); // recursively add the predecessors of the sink - backtrack_graph( n.get(), infoNodes ); + backtrack_graph( n.get(), m_node_info_map ); } else { LOG( logWARNING ) << "Sink Node " << n->instance_name() @@ -457,39 +516,40 @@ bool DataflowGraph::compile() { } // Compute the level (rank of execution) of useful nodes int maxLevel = 0; - for ( auto& infNode : infoNodes ) { - auto n = infNode.first; + for ( auto& level_and_linked : m_node_info_map ) { + auto n = level_and_linked.first; // Compute the nodes' level starting from sources if ( n->is_input() || n == m_input_node.get() ) { // set level to 0 because node is source - infNode.second.first = 0; + level_and_linked.second.first = 0; // Tag successors (go through graph) - maxLevel = std::max( maxLevel, traverse_graph( n, infoNodes ) ); + maxLevel = std::max( maxLevel, traverse_graph( n, m_node_info_map ) ); } } m_nodes_by_level.clear(); - m_nodes_by_level.resize( infoNodes.size() != 0 ? maxLevel + 1 : 0 ); - for ( auto& infNode : infoNodes ) { - CORE_ASSERT( size_t( infNode.second.first ) < m_nodes_by_level.size(), - std::string( "Node " ) + infNode.first->instance_name() + " is at level " + - std::to_string( infNode.second.first ) + " but level max is " + - std::to_string( maxLevel ) ); - - m_nodes_by_level[infNode.second.first].push_back( infNode.first ); + m_nodes_by_level.resize( m_node_info_map.size() != 0 ? maxLevel + 1 : 0 ); + for ( auto& level_and_linked : m_node_info_map ) { + CORE_ASSERT( size_t( level_and_linked.second.first ) < m_nodes_by_level.size(), + std::string( "Node " ) + level_and_linked.first->instance_name() + + " is at level " + std::to_string( level_and_linked.second.first ) + + " but level max is " + std::to_string( maxLevel ) ); + m_nodes_by_level[level_and_linked.second.first].push_back( level_and_linked.first ); } + m_active_node_count = 0; // For each level - for ( auto& lvl : m_nodes_by_level ) { + for ( auto& level : m_nodes_by_level ) { // For each node - for ( size_t j = 0; j < lvl.size(); j++ ) { - if ( !lvl[j]->compile() ) { return m_ready = false; } + m_active_node_count += level.size(); + for ( size_t j = 0; j < level.size(); j++ ) { + if ( !level[j]->compile() ) { return m_ready = false; } // For each input - for ( size_t k = 0; k < lvl[j]->inputs().size(); k++ ) { - if ( lvl[j] != m_input_node.get() && lvl[j]->inputs()[k]->is_link_mandatory() && - !lvl[j]->inputs()[k]->is_linked() ) { + for ( size_t k = 0; k < level[j]->inputs().size(); k++ ) { + if ( level[j] != m_input_node.get() && level[j]->inputs()[k]->is_link_mandatory() && + !level[j]->inputs()[k]->is_linked() ) { LOG( logERROR ) - << "Node <" << lvl[j]->instance_name() << "> is not ready" << std::endl; + << "Node <" << level[j]->instance_name() << "> is not ready" << std::endl; return m_ready = false; } } @@ -517,54 +577,47 @@ void DataflowGraph::clear_nodes() { m_should_save = true; } -void DataflowGraph::backtrack_graph( - Node* current, - std::unordered_map>>& infoNodes ) { +void DataflowGraph::backtrack_graph( Node* current, NodeInfoMap& nodes_info ) { + // loop over current node linked inputs for ( auto& input : current->inputs() ) { if ( input->link() ) { - Node* previous = input->link()->node(); - if ( previous && previous != m_input_node.get() ) { - auto previousInInfoNodes = infoNodes.find( previous ); - if ( previousInInfoNodes != infoNodes.end() ) { - // If the previous node is already in the map, - // find if the current node is already a successor node - auto& previousSuccessors = previousInInfoNodes->second.second; - bool foundCurrent = std::any_of( previousSuccessors.begin(), - previousSuccessors.end(), - [current]( auto c ) { return c == current; } ); - if ( !foundCurrent ) { + if ( auto previous = input->link()->node(); + previous && previous != m_input_node.get() ) { + // if the previous node is already in the map, + // find if the current node is already a successor node + if ( auto linked = nodes_info.find( previous ); linked != nodes_info.end() ) { + auto& successors = linked->second.second; + if ( !std::any_of( successors.begin(), successors.end(), [current]( auto c ) { + return c == current; + } ) ) { // If the current node is not a successor node, add it to the list - previousSuccessors.push_back( current ); + successors.push_back( current ); } } + // else add node and linked to nodes_infos and recurse. else { - // Add node to info nodes std::vector successors; successors.push_back( current ); - infoNodes.emplace( - previous, - std::pair>( 0, std::move( successors ) ) ); - backtrack_graph( previous, infoNodes ); + nodes_info.emplace( previous, LevelAndLinked { 0, std::move( successors ) } ); + backtrack_graph( previous, nodes_info ); } } } } } -int DataflowGraph::traverse_graph( - Node* current, - std::unordered_map>>& infoNodes ) { +int DataflowGraph::traverse_graph( Node* current, NodeInfoMap& nodes_info ) { int maxLevel = 0; - if ( infoNodes.find( current ) != infoNodes.end() ) { + if ( nodes_info.find( current ) != nodes_info.end() ) { - for ( auto const& successor : infoNodes[current].second ) { + for ( auto const& successor : nodes_info[current].second ) { // Successors is a least +1 level - infoNodes[successor].first = - std::max( infoNodes[successor].first, infoNodes[current].first + 1 ); + nodes_info[successor].first = + std::max( nodes_info[successor].first, nodes_info[current].first + 1 ); maxLevel = std::max( maxLevel, - std::max( infoNodes[successor].first, traverse_graph( successor, infoNodes ) ) ); + std::max( nodes_info[successor].first, traverse_graph( successor, nodes_info ) ) ); } } @@ -677,7 +730,7 @@ void DataflowGraph::Log::bad_port_index( const std::string& type, } void DataflowGraph::Log::try_to_link_input_to_output() { - LOG( logERROR ) << "DataflowGraph could not link input to ouput directrly"; + LOG( logERROR ) << "DataflowGraph could not link input to ouput directly"; } } // namespace Core diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index c8e2d2b9826..88d31506402 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace Ra { @@ -23,6 +24,7 @@ namespace Core { class RA_DATAFLOW_CORE_API DataflowGraph : public Node { public: + using LogCallback = std::function; /** The nodes pointing to external data are created here. * \param name The name of the render graph. */ @@ -183,7 +185,7 @@ class RA_DATAFLOW_CORE_API DataflowGraph : public Node * This input port could then be used through setter->set_default_value( data ) to set the * graph input from the data. * \note The raw pointer is only valid as graph is valid. - * \param nodeNome The name of the node + * \param nodeName The name of the node * \param portName The name of the input port * \return the port if exists, nullptr otherwise. */ @@ -196,7 +198,7 @@ class RA_DATAFLOW_CORE_API DataflowGraph : public Node * The return port can be use as in port->data(). * \note ownership is left to the graph, not shared. The graph must survive the returned * raw pointer to be able to use the dataGetter. - * \param nodeNome The name of the node + * \param nodeName The name of the node * \param portName The name of the output port * \return the port if exists, nullptr otherwise. */ @@ -222,13 +224,13 @@ class RA_DATAFLOW_CORE_API DataflowGraph : public Node * \param on true to protect, false to unprotect. * \todo should this be only on gui side ? */ - void setNodesAndLinksProtection( bool on ) { m_nodesAndLinksProtected = on; } + void setNodesAndLinksProtection( bool on ) { m_nodes_and_links_protected = on; } /** * \brief get the protection status protect nodes and links from deletion * \return the protection status */ - bool nodesAndLinksProtection() const { return m_nodesAndLinksProtected; } + bool nodesAndLinksProtection() const { return m_nodes_and_links_protected; } using Node::add_input; using Node::add_output; @@ -255,6 +257,8 @@ class RA_DATAFLOW_CORE_API DataflowGraph : public Node std::shared_ptr output_node() { return m_output_node; } std::shared_ptr input_node() { return m_input_node; } + void set_log_callback( LogCallback callback ) { m_log_callback = std::move( callback ); } + protected: /** * \brief Allow derived class to construct the graph with their own static type. @@ -279,19 +283,24 @@ class RA_DATAFLOW_CORE_API DataflowGraph : public Node bool contains_node_recursive( const Node* node ) const; private: + bool execute2(); + + /// Node -> level, linked nodes + using LevelAndLinked = std::pair>; + using NodeInfoMap = std::unordered_map; + // Internal helper functions /// Internal compilation function that allows to go back in the render graph while filling - /// an information map. \param current The current node. \param infoNodes The map that - /// contains information about nodes. - void - backtrack_graph( Node* current, - std::unordered_map>>& infoNodes ); + /// an information map. + /// \param current The current node. + /// \param infoNodes The map that contains information about nodes. + void backtrack_graph( Node* current, NodeInfoMap& nodes_info ); + /// Internal compilation function that allows to go through the graph, using an /// information map. /// \param current The current node. /// \param infoNodes The map that contains information about nodes. - int traverse_graph( Node* current, - std::unordered_map>>& infoNodes ); + int traverse_graph( Node* current, NodeInfoMap& nodes_info ); /// to allow dynamic creation of ports on input/output nodes, only possible new port is last /// port of the given node. @@ -339,11 +348,20 @@ class RA_DATAFLOW_CORE_API DataflowGraph : public Node std::shared_ptr m_output_node { nullptr }; std::shared_ptr m_input_node { nullptr }; + /// Node -> level, linked nodes + NodeInfoMap m_node_info_map; + /// The list of nodes ordered by levels. /// Two nodes at the same level have no dependency between them. std::vector> m_nodes_by_level; - bool m_nodesAndLinksProtected { false }; + /// How many node to execute in the graph. + size_t m_active_node_count { 0 }; + /// How many node executed in the current execution + std::atomic_size_t m_executed_node_count { 0 }; + bool m_nodes_and_links_protected { false }; + LogCallback m_log_callback { []( size_t, size_t ) {} }; + // []( size_t c, size_t t ) { std::cerr << "progress " << c << "/" << t << "\n"; } }; }; // ----------------------------------------------------------------- diff --git a/src/Dataflow/Core/Functionals/BinaryOpNode.hpp b/src/Dataflow/Core/Functionals/BinaryOpNode.hpp index d7d0c2c500a..b7db468fbb8 100644 --- a/src/Dataflow/Core/Functionals/BinaryOpNode.hpp +++ b/src/Dataflow/Core/Functionals/BinaryOpNode.hpp @@ -62,7 +62,7 @@ template ::value, bool it_out = Ra::Core::Utils::is_container::value> struct ExecutorHelper { - static t_out executeInternal( t_a&, t_b&, funcType ) { + static t_out executeInternal( const t_a&, const t_b&, funcType ) { static_assert( ( ( it_a || it_b ) ? it_out : !it_out ), "Invalid template parameter " ); } }; @@ -72,7 +72,7 @@ struct ExecutorHelper { */ template struct ExecutorHelper { - static t_out executeInternal( t_a& a, t_b& b, funcType f ) { + static t_out executeInternal( const t_a& a, const t_b& b, funcType f ) { t_out res; std::transform( a.begin(), a.end(), @@ -90,7 +90,7 @@ struct ExecutorHelper { */ template struct ExecutorHelper { - static t_out executeInternal( t_a& a, t_b& b, funcType f ) { + static t_out executeInternal( const t_a& a, const t_b& b, funcType f ) { t_out res; std::transform( a.begin(), a.end(), @@ -106,7 +106,7 @@ struct ExecutorHelper { */ template struct ExecutorHelper { - static t_out executeInternal( t_a& a, t_b& b, funcType f ) { + static t_out executeInternal( const t_a& a, const t_b& b, funcType f ) { t_out res; std::transform( b.begin(), b.end(), @@ -122,7 +122,7 @@ struct ExecutorHelper { */ template struct ExecutorHelper { - static t_out executeInternal( t_a& a, t_b& b, funcType f ) { return f( a, b ); } + static t_out executeInternal( const t_a& a, const t_b& b, funcType f ) { return f( a, b ); } }; } // namespace internal diff --git a/src/Dataflow/Core/Functionals/TransformNode.hpp b/src/Dataflow/Core/Functionals/TransformNode.hpp index a156bb56da8..70affa4419a 100644 --- a/src/Dataflow/Core/Functionals/TransformNode.hpp +++ b/src/Dataflow/Core/Functionals/TransformNode.hpp @@ -99,7 +99,7 @@ bool TransformNode::execute() { m_result.clear(); // m_elements.reserve( inData.size() ); // --> this is not a requirement of // SequenceContainer - std::transform( inData.begin(), inData.end(), std::back_inserter( m_result ), f ); + std::transform( inData.cbegin(), inData.cend(), std::back_inserter( m_result ), f ); return true; } diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index a3883e2dcec..b909d994c78 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -8,6 +8,9 @@ class PortBase; using namespace Ra::Core::Utils; +RA_SINGLETON_IMPLEMENTATION( NodeJsonSerializer ); +RA_SINGLETON_IMPLEMENTATION( NodeJsonDeserializer ); + // display_name is instanceName unless reset afterward Node::Node( const std::string& instanceName, const std::string& typeName ) : m_model_name { typeName }, m_instance_name { instanceName }, m_display_name { instanceName } {} @@ -103,6 +106,13 @@ bool Node::fromJsonInternal( const nlohmann::json& data ) { m_outputs[index]->from_json( port ); } } + + if ( const auto& params = data.find( "params" ); params != data.end() ) { + + auto visitor = NodeJsonDeserializer::getInstance(); + visitor->set_json( *params ); + m_parameters.visit( *visitor ); + } return true; } @@ -126,6 +136,11 @@ void Node::toJsonInternal( nlohmann::json& data ) const { port["type"] = Ra::Core::Utils::simplifiedDemangledType( p->type() ); data["outputs"].push_back( port ); } + + auto visitor = NodeJsonSerializer::getInstance(); + visitor->clear(); + m_parameters.visit( *visitor ); + data["params"] = visitor->json(); LOG( Ra::Core::Utils::logDEBUG ) << message; } diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index c2171d10157..a22c05e1363 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -18,6 +19,76 @@ namespace Ra { namespace Dataflow { namespace Core { +// ----------------------------------------------------------------- +// ---------------------- helper classes --------------------------- + +class NodeJsonSerializer : public Ra::Core::DynamicVisitor +{ + public: + RA_SINGLETON_INTERFACE( NodeJsonSerializer ); + + using DynamicVisitor::operator(); + template + void operator()( const std::string& name, T& _in, std::any&& ) { + nlohmann::json p; + p["type"] = Ra::Core::Utils::simplifiedDemangledType(); + p["value"] = _in; + p["name"] = name; + m_json.push_back( p ); + } + /// call clear before visit to clear json content. + void clear() { m_json.clear(); } + const nlohmann::json& json() { return m_json; } + + private: + NodeJsonSerializer() : DynamicVisitor() { + addOperator( *this ); + addOperator( *this ); + addOperator( *this ); + addOperator( *this ); + } + + nlohmann::json m_json; +}; + +class NodeJsonDeserializer : public Ra::Core::DynamicVisitor +{ + public: + RA_SINGLETON_INTERFACE( NodeJsonDeserializer ); + + using DynamicVisitor::operator(); + template + void operator()( const std::string& name, T& _in, std::any&& ) { + for ( const auto& p : m_json ) { + if ( p["name"] == name ) { + if ( p["type"] == Ra::Core::Utils::simplifiedDemangledType() ) { + p.at( "value" ).get_to( _in ); + return; + } + else { + // LOG( logERROR ) + // << "Read json, bad type for parameter " << name << " + // got " << p["type"] + // << " instead of " << + // Ra::Core::Utils::simplifiedDemangledType() << "\n"; + } + } + } + } + + void set_json( const nlohmann::json& json ) { m_json = json; } + + private: + NodeJsonDeserializer() : DynamicVisitor() { + addOperator( *this ); + addOperator( *this ); + addOperator( *this ); + addOperator( *this ); + } + + nlohmann::json m_json; +}; + /** * \brief Base abstract class for all the nodes added and used by the node system. * @@ -153,7 +224,7 @@ class RA_DATAFLOW_CORE_API Node * \brief Get a port by its index * * \param type either "in" or "out", the directional type of the port - * \param index Index of the port \in [0 get(Inputs|Output).size()[ + * \param index Index of the port \f$ \in [0, get(Inputs|Output).size()[ \f$ * \return a raw pointer on the requested port if it exists, nullptr else */ PortBaseRawPtr port_by_index( const std::string& type, PortIndex index ) const; @@ -246,16 +317,17 @@ class RA_DATAFLOW_CORE_API Node Ra::Core::VariableSet& input_variables(); /// \brief Node is output if none of the output ports is linked. - inline bool is_output(); + inline bool is_output() const; /// \brief Node is input if all input ports have default values and not linked. - inline bool is_input(); + inline bool is_input() const; protected: + virtual void add_enum_converters() {}; /** * \brief Construct the base node given its name and type. * - * \param instanceName The name of the node, unique in a graph + * \param instance The name of the node, unique in a graph * \param typeName The type name of the node, from static typename() concrete node class. */ Node( const std::string& instance, const std::string& typeName ); @@ -289,7 +361,7 @@ class RA_DATAFLOW_CORE_API Node * \tparam T The contained type. * \tparam PortType PortBaseIn or PortBaseOut * \param ports The port collection - * \param idx + * \param index The port index * \return auto A raw ptr to the port typed in or out. */ template @@ -300,30 +372,32 @@ class RA_DATAFLOW_CORE_API Node /*else*/ PortOutRawPtr>::type>( port_base( ports, index ) ); } /** - * \brief Internal json representation of the Node. + * \brief Internal json representation of the Node. * - * Default implementation warn about unsupported deserialization. - * Effective deserialzation must be implemented by inheriting classes. - * Be careful with template specialization and function member overriding in derived classes. + * Default implementation warn about default serialization. + * Be careful with template specialization and function member overriding in derived + * classes. + * Default implementation call from_json for each input port, output port and paramteres. */ virtual bool fromJsonInternal( const nlohmann::json& data ); /** - * \brief Internal json representation of the Node. + * \brief Internal json representation of the Node. * - * Default implementation warn about unsupported deserialization. - * Effective deserialzation must be implemented by inheriting classes. - * Be careful with template specialization and function member overriding in derived classes. + * Default implementation warn about default deserialization. + * Be careful with template specialization and function member overriding in derived + * classes. + * Default implementation call to_json for each input port, output port and paramteres. */ virtual void toJsonInternal( nlohmann::json& data ) const; /** * \brief Adds a port to port collection * * \param port The in port to add. - * \param coll Port collection (input or output) + * \param ports Port collection (input or output) * \return The index of the inserted port. */ template - PortIndex add_port( PortCollection>&, PortPtr port ); + PortIndex add_port( PortCollection>& ports, PortPtr port ); /// Convenience alias to add_port(inputs(), in) PortIndex add_input( PortBaseInPtr in ); /// Convenience alias to add_port(outputs(), out) @@ -498,11 +572,12 @@ inline Ra::Core::VariableSet& Node::input_variables() { for ( const auto& p : m_inputs ) { if ( p->has_default_value() ) p->insert( m_input_variables ); } + add_enum_converters(); return m_input_variables; } -inline bool Node::is_output() { +inline bool Node::is_output() const { bool ret = true; for ( const auto& p : m_outputs ) { ret = ret && ( p->link_count() == 0 ); @@ -510,7 +585,7 @@ inline bool Node::is_output() { return ret; } -inline bool Node::is_input() { +inline bool Node::is_input() const { bool ret = true; // for ( const auto& p : m_inputs ) { diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index 04660ac6908..cc8d0135f75 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -70,6 +70,7 @@ class RA_DATAFLOW_CORE_API NodeFactory * \param nodeType the name of the concrete type * (the same as what is obtained by T::node_typename() on a node of type T) * \param nodeCreator Functor to create an node of the corresponding concrete node type. + * \param nodeCategory Category of the node. * \return true if the node creator is successfully added, false if not (e.g. due to a name * collision). */ diff --git a/src/Dataflow/Core/PortIn.hpp b/src/Dataflow/Core/PortIn.hpp index 68b13fb6668..e78533cddfb 100644 --- a/src/Dataflow/Core/PortIn.hpp +++ b/src/Dataflow/Core/PortIn.hpp @@ -87,7 +87,7 @@ class PortIn : public PortBaseIn, PortIn( Node* node, const std::string& name ) : PortBaseIn( node, name, typeid( T ) ) { add_port_type(); } - /// \copydoc PortIn + /// \copydoc PortIn( Node* node, const std::string& name ) /// \param value default value of the port PortIn( Node* node, const std::string& name, const T& value ) : PortBaseIn( node, name, typeid( T ) ), m_defaultValue { value } { @@ -150,7 +150,12 @@ class PortIn : public PortBaseIn, void from_json_impl( const nlohmann::json& data ) { using namespace Ra::Core::Utils; if ( auto value_it = data.find( "default_value" ); value_it != data.end() ) { - set_default_value( ( *value_it ).template get() ); + try { + set_default_value( ( *value_it ).template get() ); + } + catch ( ... ) { + LOG( logERROR ) << "failed to read json default value for " << data["name"]; + } } } template - void set_data( T* data ); + void set_data( const T* data ); // called by PortIn when connect virtual void increase_link_count() { @@ -49,7 +49,7 @@ class RA_DATAFLOW_CORE_API PortBaseOut : public PortBase --m_linkCount; CORE_ASSERT( m_linkCount >= 0, "link count error" ); } - virtual int link_count() { return m_linkCount; } + virtual int link_count() const { return m_linkCount; } protected: /// Constructor. @@ -132,7 +132,7 @@ T& PortBaseOut::data() { } template -void PortBaseOut::set_data( T* data ) { +void PortBaseOut::set_data( const T* data ) { auto thisOut = dynamic_cast*>( this ); if ( thisOut ) { thisOut->set_data( data ); diff --git a/src/Dataflow/Core/Sources/FunctionSource.hpp b/src/Dataflow/Core/Sources/FunctionSource.hpp index 2c8d547bf27..ceffdcf6b09 100644 --- a/src/Dataflow/Core/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Sources/FunctionSource.hpp @@ -25,6 +25,7 @@ class FunctionSourceNode : public Node explicit FunctionSourceNode( const std::string& name ) : FunctionSourceNode( name, FunctionSourceNode::node_typename() ) {} + static const std::string& node_typename(); bool execute() override; @@ -39,6 +40,9 @@ class FunctionSourceNode : public Node */ function_type* data() const; + RA_NODE_PORT_IN( function_type, from ); + RA_NODE_PORT_OUT( function_type, to ); + protected: FunctionSourceNode( const std::string& instanceName, const std::string& typeName ); @@ -46,12 +50,6 @@ class FunctionSourceNode : public Node return Node::fromJsonInternal( data ); } void toJsonInternal( nlohmann::json& data ) const override { Node::toJsonInternal( data ); } - - RA_NODE_PORT_IN( function_type, from ); - RA_NODE_PORT_OUT( function_type, to ); - - public: - static const std::string& node_typename(); }; // ----------------------------------------------------------------- diff --git a/src/Dataflow/Core/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Sources/SingleDataSourceNode.hpp index 996b4a81226..ad1b0ff17bd 100644 --- a/src/Dataflow/Core/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Sources/SingleDataSourceNode.hpp @@ -4,7 +4,6 @@ #include #include -#include #include #include @@ -25,14 +24,13 @@ namespace Sources { template class SingleDataSourceNode : public Node { - protected: - SingleDataSourceNode( const std::string& instanceName, const std::string& typeName ); - public: // warning, hacky specialization for set editable explicit SingleDataSourceNode( const std::string& name ) : SingleDataSourceNode( name, SingleDataSourceNode::node_typename() ) {} + static const std::string& node_typename(); + bool execute() override; /** \brief Set the data to be delivered by the node. @@ -46,9 +44,14 @@ class SingleDataSourceNode : public Node * \brief Get the delivered data * @return The non owning pointer (alias) to the delivered data. */ - T* data() const; + const T* data() const; + + RA_NODE_PORT_IN( T, from ); + RA_NODE_PORT_OUT( T, to ); protected: + SingleDataSourceNode( const std::string& instanceName, const std::string& typeName ); + bool fromJsonInternal( const nlohmann::json& data ) override { return Node::fromJsonInternal( data ); } @@ -56,13 +59,6 @@ class SingleDataSourceNode : public Node void toJsonInternal( nlohmann::json& data ) const override { return Node::toJsonInternal( data ); } - - private: - RA_NODE_PORT_IN( T, from ); - RA_NODE_PORT_OUT( T, to ); - - public: - static const std::string& node_typename(); }; // ----------------------------------------------------------------- @@ -90,7 +86,7 @@ void SingleDataSourceNode::set_data( T data ) { } template -T* SingleDataSourceNode::data() const { +const T* SingleDataSourceNode::data() const { return &( m_port_in_from->data() ); } diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt index 6d3e4dae5ad..ce5aff19501 100644 --- a/src/Dataflow/QtGui/CMakeLists.txt +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -59,7 +59,7 @@ configure_radium_library( # Generate cmake package configure_radium_package( NAME ${ra_dataflowqtgui_target} PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in - PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowqtgui_target}" NAME_PREFIX "Radium" + NAME_PREFIX "Radium" ) set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowqtgui_target} PARENT_SCOPE) diff --git a/src/Dataflow/QtGui/Config.cmake.in b/src/Dataflow/QtGui/Config.cmake.in index 42e8dadec3b..365177dbd70 100644 --- a/src/Dataflow/QtGui/Config.cmake.in +++ b/src/Dataflow/QtGui/Config.cmake.in @@ -1,43 +1,29 @@ # -------------- Configuration of the Radium DataflowCore targets and definitions ----------------------- # Setup Engine and check for dependencies -if (DataflowQtGui_FOUND AND NOT TARGET DataflowQtGui) - set(Configure_DataflowQtGui ON) - # verify dependencies - if(NOT DataflowCore_FOUND) - # if in source dir - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake") - set(DataflowCore_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake) - else() - # if in install dir - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake") - set(DataflowCore_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowQtGui: dependency DataflowCore not found") - set(Configure_DataflowQtGui OFF) - endif() - endif() - endif() - if(NOT Gui_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake") - set(Gui_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowQtGui: dependency Gui not found") - set(Configure_DataflowQtGui OFF) - endif() +add_component_dependency(DataflowCore) +add_component_dependency(Gui) + +if(NOT TARGET Radium::DataflowQtGui) + include(${CMAKE_CURRENT_LIST_DIR}/QtFunctions.cmake) + check_and_set_qt_version("@QT_DEFAULT_MAJOR_VERSION@") + find_qt_dependency(COMPONENTS Core Widgets OpenGL Xml REQUIRED) + + # Theses paths reflect the paths founds in RadiumEngine/external/Gui/package + set(_BaseExternalDir_ "${CMAKE_CURRENT_LIST_DIR}/../../../../") + if("@QtNodes_DIR@" STREQUAL "") + set(QtNodes_DIR "${_BaseExternalDir_}/@QtNodes_sub_DIR@") + else() + set(QtNodes_DIR "@QtNodes_DIR@") endif() -endif() + find_dependency(QtNodes REQUIRED NO_DEFAULT_PATH) -if(Configure_DataflowQtGui) - set(RadiumNodeEditor_DIR "@RadiumNodeEditor_DIR@") - find_dependency(RadiumNodeEditor REQUIRED NO_DEFAULT_PATH) if(MSVC OR MSVC_IDE OR MINGW) - add_imported_dir(FROM RadiumNodeEditor::RadiumNodeEditor TO RadiumExternalDlls_location) + get_target_property(QTLIB Qt::Core INTERFACE_LINK_LIBRARIES) + add_imported_dir(FROM ${QTLIB} TO RadiumExternalDlls_location) + add_imported_dir(FROM QtNodes::QtNodes TO RadiumExternalDlls_location) endif() - include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/QtGui/DataflowQtGuiTargets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/DataflowQtGuiTargets.cmake") endif() + +check_target_set_found(DataflowQtGui) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphModel.cpp b/src/Dataflow/QtGui/GraphEditor/GraphModel.cpp index 24beb442aa8..6cab80d5445 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphModel.cpp @@ -14,6 +14,10 @@ GraphModel::GraphModel( std::shared_ptr graph ) : m_graph { graph }, m_next_node_id { 0 } { fill_factory_map(); sync_data(); + m_widget_factory_instanciator = + []( Ra::Core::VariableSet& vs, Ra::Gui::VariableSetEditor* ve, const nlohmann::json& p ) { + return new WidgetFactory( vs, ve, p ); + }; } GraphModel::~GraphModel() { @@ -136,7 +140,8 @@ bool GraphModel::nodeExists( NodeId const nodeId ) const { QWidget* GraphModel::getWidget( std::shared_ptr node ) const { QWidget* controlPanel = new QWidget; - controlPanel->setStyleSheet( "background-color:transparent;" ); + // controlPanel->setStyleSheet( "background-color:transparent;" ); + controlPanel->setWindowOpacity( 0.8 ); QVBoxLayout* layout = new QVBoxLayout( controlPanel ); auto node_inputs = node->input_variables(); @@ -144,8 +149,10 @@ QWidget* GraphModel::getWidget( std::shared_ptr node ) const { auto controlPanelInputs = new Ra::Gui::VariableSetEditor( "Inputs default", nullptr ); controlPanelInputs->setShowUnspecified( true ); - WidgetFactory ui_builder { node_inputs, controlPanelInputs, {} }; - node_inputs.visit( ui_builder ); + WidgetFactory* ui_builder //{ node_inputs, controlPanelInputs, {} }; + = m_widget_factory_instanciator( node_inputs, controlPanelInputs, {} ); + node_inputs.visit( *ui_builder ); + delete ui_builder; // empty controlPanel has 3 children, if no more, then no widget for the inputs if ( controlPanelInputs->children().size() > 3 ) layout->addWidget( controlPanelInputs ); @@ -155,10 +162,10 @@ QWidget* GraphModel::getWidget( std::shared_ptr node ) const { if ( node->parameters().size() > 0 ) { auto controlPanelParams = new Ra::Gui::VariableSetEditor( "Parameters", nullptr ); controlPanelParams->setShowUnspecified( true ); - - WidgetFactory ui_builder { node->parameters(), controlPanelParams, {} }; - node_inputs.visit( ui_builder ); - + WidgetFactory* ui_builder //{ node->parameters(), controlPanelParams, {} }; + = m_widget_factory_instanciator( node->parameters(), controlPanelParams, {} ); + node->parameters().visit( *ui_builder ); + delete ui_builder; layout->addWidget( controlPanelParams ); } auto g = dynamic_cast( node.get() ); diff --git a/src/Dataflow/QtGui/GraphEditor/GraphModel.hpp b/src/Dataflow/QtGui/GraphEditor/GraphModel.hpp index cc80d36d876..deec68f1f7a 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphModel.hpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphModel.hpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include @@ -105,7 +107,12 @@ class RA_DATAFLOW_GUI_API GraphModel : public QtNodes::AbstractGraphModel auto node_ptr( NodeId node_id ) -> std::shared_ptr { return m_node_id_to_ptr.at( node_id ); } - + using WidgetFactoryInstanciator = std::function; + void set_widget_factory_instanciator( WidgetFactoryInstanciator widget_factory_instanciator ) { + m_widget_factory_instanciator = widget_factory_instanciator; + } signals: void node_edited( std::shared_ptr node ); @@ -126,6 +133,8 @@ class RA_DATAFLOW_GUI_API GraphModel : public QtNodes::AbstractGraphModel void fill_factory_map(); std::map m_model_name_to_factory; + + WidgetFactoryInstanciator m_widget_factory_instanciator; }; } // namespace GraphEditor } // namespace QtGui diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index 75938a4c7bd..90f7bc291db 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -6,11 +6,17 @@ namespace QtGui { namespace GraphEditor { void WidgetFactory::operator()( const std::string& name, std::string& p ) { - auto line = new QLineEdit(); + auto frame = new QFrame(); + auto layout = new QHBoxLayout(); + frame->setLayout( layout ); + auto line = new QLineEdit( frame ); + auto label = new QLabel( QString::fromStdString( name ), frame ); line->setObjectName( QString::fromStdString( name ) ); QLineEdit::connect( line, &QLineEdit::textEdited, [&p]( const QString& string ) { p = string.toStdString(); } ); - variable_set_editor()->addWidget( line ); + layout->addWidget( label ); + layout->addWidget( line ); + variable_set_editor()->addWidget( frame ); } #if HAS_TRANSFER_FUNCTION diff --git a/src/Dataflow/Rendering/CMakeLists.txt b/src/Dataflow/Rendering/CMakeLists.txt index c5e2b13f0e1..02e4a60ca0f 100644 --- a/src/Dataflow/Rendering/CMakeLists.txt +++ b/src/Dataflow/Rendering/CMakeLists.txt @@ -29,7 +29,7 @@ configure_radium_library( # Generate cmake package configure_radium_package( NAME ${ra_dataflowrendering_target} PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in - PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowrendering_target}" NAME_PREFIX "Radium" + NAME_PREFIX "Radium" ) set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowrendering_target} PARENT_SCOPE) diff --git a/src/Engine/CMakeLists.txt b/src/Engine/CMakeLists.txt index 2eaff1e5600..fa5bca9a544 100644 --- a/src/Engine/CMakeLists.txt +++ b/src/Engine/CMakeLists.txt @@ -30,8 +30,9 @@ target_compile_definitions(${ra_engine_target} PUBLIC GLOBJECTS_USE_EIGEN) target_compile_options(${ra_engine_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) add_dependencies(${ra_engine_target} Core RadiumEngineShaders) + target_link_libraries( - ${ra_engine_target} PUBLIC Core glbinding::glbinding glbinding::glbinding-aux + ${ra_engine_target} PUBLIC Radium::Core glbinding::glbinding glbinding::glbinding-aux globjects::globjects tinyEXR::tinyEXR ) diff --git a/src/Engine/Config.cmake.in b/src/Engine/Config.cmake.in index 0c990b27764..d6cfb1a8535 100644 --- a/src/Engine/Config.cmake.in +++ b/src/Engine/Config.cmake.in @@ -2,24 +2,10 @@ # -------------- Configuration of the Radium Engine targets and definitions ----------------------- # Setup Engine and check for dependencies -# TODO: verify if this is needed -if (Engine_FOUND AND NOT TARGET Engine) - set(Configure_Engine ON) - # verify dependencies - if(NOT Core_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake") - set(Core_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Engine: dependency Core not found") - set(Configure_Engine OFF) - endif() - endif() -endif() +add_component_dependency(Core) # configure Engine component -if(Configure_Engine) +if(NOT TARGET Radium::Engine) set(glbinding_DIR "@glbinding_DIR@") set(globjects_DIR "@globjects_DIR@") set(tinyEXR_DIR "@tinyEXR_DIR@") @@ -33,3 +19,5 @@ if(Configure_Engine) endif() include("${CMAKE_CURRENT_LIST_DIR}/EngineTargets.cmake") endif() + +check_target_set_found(Engine) diff --git a/src/Engine/Scene/CameraManager.cpp b/src/Engine/Scene/CameraManager.cpp index e705c76c8ea..68ccb7442e9 100644 --- a/src/Engine/Scene/CameraManager.cpp +++ b/src/Engine/Scene/CameraManager.cpp @@ -72,17 +72,17 @@ size_t CameraManager::count() const { return m_data->size(); } +class RoUpdater : public Ra::Core::Task +{ + public: + void process() override { m_camera->updateTransform(); } + std::string getName() const override { return "camera updater"; } + CameraComponent* m_camera; +}; + void CameraManager::generateTasks( Core::TaskQueue* taskQueue, const Engine::FrameInfo& /*frameInfo*/ ) { - class RoUpdater : public Ra::Core::Task - { - public: - void process() override { m_camera->updateTransform(); } - std::string getName() const override { return "camera updater"; } - CameraComponent* m_camera; - }; - // only update visible components. for ( size_t i = 0; i < m_data->size(); ++i ) { auto comp = ( *m_data )[i]; diff --git a/src/Gui/Config.cmake.in b/src/Gui/Config.cmake.in index 8c963d13235..5061ed63c1b 100644 --- a/src/Gui/Config.cmake.in +++ b/src/Gui/Config.cmake.in @@ -1,34 +1,13 @@ # -------------- Configuration of the Radium Gui targets and definitions ------- # Setup Gui and check for dependencies -# TODO : verify if this is needed -if(Gui_FOUND AND NOT TARGET Gui) - set(Configure_Gui ON) - # verify dependencies - if(NOT Engine_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake") - set(Engine_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Gui: dependency Engine not found") - set(Configure_Gui OFF) - endif() - endif() - if(Configure_Gui AND NOT IO_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../IO/RadiumIOConfig.cmake") - set(IO_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../IO/RadiumIOConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Gui: dependency IO not found") - set(Configure_Gui OFF) - endif() - endif() -endif() +add_component_dependency(Core) +add_component_dependency(Engine) +add_component_dependency(PluginBase) +add_component_dependency(IO) -if(Configure_Gui) - include(${CMAKE_CURRENT_LIST_DIR}/../QtFunctions.cmake) +if(NOT TARGET Radium::Gui) + include(${CMAKE_CURRENT_LIST_DIR}/QtFunctions.cmake) check_and_set_qt_version("@QT_DEFAULT_MAJOR_VERSION@") find_qt_dependency(COMPONENTS Core Widgets OpenGL Xml REQUIRED) @@ -41,9 +20,9 @@ if(Configure_Gui) endif() find_dependency(PowerSlider REQUIRED NO_DEFAULT_PATH) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../PluginBase/RadiumPluginBaseConfig.cmake") + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/RadiumPluginBaseConfig.cmake") set(PluginBase_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../PluginBase/RadiumPluginBaseConfig.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/RadiumPluginBaseConfig.cmake) endif() if(MSVC OR MSVC_IDE OR MINGW) @@ -53,3 +32,5 @@ if(Configure_Gui) endif() include("${CMAKE_CURRENT_LIST_DIR}/GuiTargets.cmake") endif() + +check_target_set_found(Gui) diff --git a/src/Gui/ParameterSetEditor/BasicUiBuilder.hpp b/src/Gui/ParameterSetEditor/BasicUiBuilder.hpp index 0b52b7ade8b..8ddb0e3496c 100644 --- a/src/Gui/ParameterSetEditor/BasicUiBuilder.hpp +++ b/src/Gui/ParameterSetEditor/BasicUiBuilder.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace Ra { @@ -77,6 +78,23 @@ class BasicUiBuilder : public Ra::Core::DynamicVisitor } } + template ::value, bool> = true> + void operator()( const std::string& name, TParam& p ) { + using namespace Ra::Core::VariableSetEnumManagement; + std::cerr << "ui builder enum " << name << "\n"; + // if ( getEnumConverter( m_params, name ) ) { + // known enum + m_pse->addEnumWidget( name, p, m_params, m_constraints ); + // } + // else { + // std::cerr << "non arithmetic unknown enum\n"; + // unknown enum, try to convert ? + // to be fixed + // m_pse->addNumberWidget>( name, p, m_params, + // m_constraints ); + // } + std::cerr << "end ui builder enum " << name << "\n"; + } template ::value, bool> = true> @@ -103,7 +121,6 @@ class BasicUiBuilder : public Ra::Core::DynamicVisitor template void operator()( const std::string& name, Eigen::Matrix& p ) { - std::cerr << "ui builder matrix\n"; m_pse->addMatrixWidget( name, p, m_params, m_constraints ); } diff --git a/src/Gui/ParameterSetEditor/ParameterSetEditor.hpp b/src/Gui/ParameterSetEditor/ParameterSetEditor.hpp index 144cbe0415f..5db985dced6 100644 --- a/src/Gui/ParameterSetEditor/ParameterSetEditor.hpp +++ b/src/Gui/ParameterSetEditor/ParameterSetEditor.hpp @@ -137,12 +137,17 @@ void VariableSetEditor::addEnumWidget( const std::string& key, Core::VariableSet& params, const nlohmann::json& metadata ) { using namespace Ra::Core::VariableSetEnumManagement; - auto m = metadata[key]; - std::string description = m.contains( "description" ) ? m["description"] : ""; - std::string nm = m.contains( "name" ) ? std::string { m["name"] } : key; + std::string description = ""; + std::string nm = key; + if ( metadata.contains( key ) ) { + auto m = metadata[key]; + description = m.contains( "description" ) ? m["description"] : ""; + nm = m.contains( "name" ) ? std::string { m["name"] } : key; + } if ( auto ec = getEnumConverter( params, key ) ) { + auto items = ( *ec )->getEnumerators(); auto onEnumParameterStringChanged = [this, ¶ms, &key]( const QString& value ) { // params.setVariable( key, value.toStdString() ); @@ -151,25 +156,31 @@ void VariableSetEditor::addEnumWidget( const std::string& key, }; addComboBox( nm, onEnumParameterStringChanged, - getEnumString( params, key, initial ), + ( *ec )->getEnumerator( initial ), items, description ); } else { LOG( Core::Utils::logWARNING ) << "ParameterSet don't have converter for enum " << key << " use index<>int instead."; + if ( metadata.contains( key ) ) { + auto m = metadata[key]; + auto items = std::vector(); + items.reserve( m["values"].size() ); + for ( const auto& value : m["values"] ) { + items.push_back( value ); + } - auto items = std::vector(); - items.reserve( m["values"].size() ); - for ( const auto& value : m["values"] ) { - items.push_back( value ); + auto onEnumParameterIntChanged = [this, ¶ms, &key]( int value ) { + params.setVariable( key, value ); + emit parameterModified( key ); + }; + addComboBox( nm, onEnumParameterIntChanged, int( initial ), items, description ); + } + else { + LOG( Core::Utils::logWARNING ) + << "Metadata missing for " << key << " no combo box added\n"; } - - auto onEnumParameterIntChanged = [this, ¶ms, &key]( T value ) { - params.setVariable( key, value ); - emit parameterModified( key ); - }; - addComboBox( nm, onEnumParameterIntChanged, initial, items, description ); } } diff --git a/src/Headless/CLI/App.hpp b/src/Headless/CLI/App.hpp deleted file mode 100644 index 0ceb177a791..00000000000 --- a/src/Headless/CLI/App.hpp +++ /dev/null @@ -1,3156 +0,0 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner -// under NSF AWARD 1414736 and by the respective contributors. -// All rights reserved. -// -// SPDX-License-Identifier: BSD-3-Clause - -#pragma once - -// [CLI11:public_includes:set] -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// [CLI11:public_includes:end] - -// CLI Library includes -#include "ConfigFwd.hpp" -#include "Error.hpp" -#include "FormatterFwd.hpp" -#include "Macros.hpp" -#include "Option.hpp" -#include "Split.hpp" -#include "StringTools.hpp" -#include "TypeTools.hpp" - -namespace CLI { -// [CLI11:app_hpp:verbatim] - -#ifndef CLI11_PARSE -# define CLI11_PARSE( app, argc, argv ) \ - try { \ - ( app ).parse( ( argc ), ( argv ) ); \ - } \ - catch ( const CLI::ParseError& e ) { \ - return ( app ).exit( e ); \ - } -#endif - -namespace detail { -enum class Classifier { - NONE, - POSITIONAL_MARK, - SHORT, - LONG, - WINDOWS_STYLE, - SUBCOMMAND, - SUBCOMMAND_TERMINATOR -}; -struct AppFriend; -} // namespace detail - -namespace FailureMessage { -std::string simple( const App* app, const Error& e ); -std::string help( const App* app, const Error& e ); -} // namespace FailureMessage - -/// enumeration of modes of how to deal with extras in config files - -enum class config_extras_mode : char { error = 0, ignore, capture }; - -class App; - -using App_p = std::shared_ptr; - -class Option_group; -/// Creates a command line program, with very few defaults. -/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The - * templated add_option methods make it easy to prepare options. Remember to call `.start` before - * starting your program, so that the options can be evaluated and the help option doesn't - * accidentally run your program. */ -class App -{ - friend Option; - friend detail::AppFriend; - - protected: - // This library follows the Google style guide for member names ending in underscores - - /// \name Basics - ///\{ - - /// Subcommand name or program name (from parser if name is empty) - std::string name_ {}; - - /// Description of the current program/subcommand - std::string description_ {}; - - /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE - bool allow_extras_ { false }; - - /// If ignore, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE - /// if error error on an extra argument, and if capture feed it to the app - config_extras_mode allow_config_extras_ { config_extras_mode::ignore }; - - /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE - bool prefix_command_ { false }; - - /// If set to true the name was automatically generated from the command line vs a user set name - bool has_automatic_name_ { false }; - - /// If set to true the subcommand is required to be processed and used, ignored for main app - bool required_ { false }; - - /// If set to true the subcommand is disabled and cannot be used, ignored for main app - bool disabled_ { false }; - - /// Flag indicating that the pre_parse_callback has been triggered - bool pre_parse_called_ { false }; - - /// Flag indicating that the callback for the subcommand should be executed immediately on parse - /// completion which is before help or ini files are processed. INHERITABLE - bool immediate_callback_ { false }; - - /// This is a function that runs prior to the start of parsing - std::function pre_parse_callback_ {}; - - /// This is a function that runs when parsing has finished. - std::function parse_complete_callback_ {}; - /// This is a function that runs when all processing has completed - std::function final_callback_ {}; - - ///\} - /// \name Options - ///\{ - - /// The default values for options, customizable and changeable INHERITABLE - OptionDefaults option_defaults_ {}; - - /// The list of options, stored locally - std::vector options_ {}; - - ///\} - /// \name Help - ///\{ - - /// Footer to put after all options in the help output INHERITABLE - std::string footer_ {}; - - /// This is a function that generates a footer to put after all other options in help output - std::function footer_callback_ {}; - - /// A pointer to the help flag if there is one INHERITABLE - Option* help_ptr_ { nullptr }; - - /// A pointer to the help all flag if there is one INHERITABLE - Option* help_all_ptr_ { nullptr }; - - /// A pointer to a version flag if there is one - Option* version_ptr_ { nullptr }; - - /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) - std::shared_ptr formatter_ { new Formatter() }; - - /// The error message printing function INHERITABLE - std::function failure_message_ { - FailureMessage::simple }; - - ///\} - /// \name Parsing - ///\{ - - using missing_t = std::vector>; - - /// Pair of classifier, string for missing options. (extra detail is removed on returning from - /// parse) - /// - /// This is faster and cleaner than storing just a list of strings and reparsing. This may - /// contain the -- separator. - missing_t missing_ {}; - - /// This is a list of pointers to options with the original parse order - std::vector parse_order_ {}; - - /// This is a list of the subcommands collected, in order - std::vector parsed_subcommands_ {}; - - /// this is a list of subcommands that are exclusionary to this one - std::set exclude_subcommands_ {}; - - /// This is a list of options which are exclusionary to this App, if the options were used this - /// subcommand should not be - std::set exclude_options_ {}; - - /// this is a list of subcommands or option groups that are required by this one, the list is - /// not mutual, the listed subcommands do not require this one - std::set need_subcommands_ {}; - - /// This is a list of options which are required by this app, the list is not mutual, listed - /// options do not need the subcommand not be - std::set need_options_ {}; - - ///\} - /// \name Subcommands - ///\{ - - /// Storage for subcommand list - std::vector subcommands_ {}; - - /// If true, the program name is not case sensitive INHERITABLE - bool ignore_case_ { false }; - - /// If true, the program should ignore underscores INHERITABLE - bool ignore_underscore_ { false }; - - /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. - /// INHERITABLE - bool fallthrough_ { false }; - - /// Allow '/' for options for Windows like options. Defaults to true on Windows, false - /// otherwise. INHERITABLE - bool allow_windows_style_options_ { -#ifdef _WIN32 - true -#else - false -#endif - }; - /// specify that positional arguments come at the end of the argument sequence not inheritable - bool positionals_at_end_ { false }; - - enum class startup_mode : char { stable, enabled, disabled }; - /// specify the startup mode for the app - /// stable=no change, enabled= startup enabled, disabled=startup disabled - startup_mode default_startup { startup_mode::stable }; - - /// if set to true the subcommand can be triggered via configuration files INHERITABLE - bool configurable_ { false }; - - /// If set to true positional options are validated before assigning INHERITABLE - bool validate_positionals_ { false }; - - /// indicator that the subcommand is silent and won't show up in subcommands list - /// This is potentially useful as a modifier subcommand - bool silent_ { false }; - - /// Counts the number of times this command/subcommand was parsed - std::uint32_t parsed_ { 0U }; - - /// Minimum required subcommands (not inheritable!) - std::size_t require_subcommand_min_ { 0 }; - - /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited - /// INHERITABLE - std::size_t require_subcommand_max_ { 0 }; - - /// Minimum required options (not inheritable!) - std::size_t require_option_min_ { 0 }; - - /// Max number of options allowed. 0 is unlimited (not inheritable) - std::size_t require_option_max_ { 0 }; - - /// A pointer to the parent if this is a subcommand - App* parent_ { nullptr }; - - /// The group membership INHERITABLE - std::string group_ { "Subcommands" }; - - /// Alias names for the subcommand - std::vector aliases_ {}; - - ///\} - /// \name Config - ///\{ - - /// Pointer to the config option - Option* config_ptr_ { nullptr }; - - /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) - std::shared_ptr config_formatter_ { new ConfigTOML() }; - - ///\} - - /// Special private constructor for subcommand - App( std::string app_description, std::string app_name, App* parent ) : - name_( std::move( app_name ) ), - description_( std::move( app_description ) ), - parent_( parent ) { - // Inherit if not from a nullptr - if ( parent_ != nullptr ) { - if ( parent_->help_ptr_ != nullptr ) - set_help_flag( parent_->help_ptr_->get_name( false, true ), - parent_->help_ptr_->get_description() ); - if ( parent_->help_all_ptr_ != nullptr ) - set_help_all_flag( parent_->help_all_ptr_->get_name( false, true ), - parent_->help_all_ptr_->get_description() ); - - /// OptionDefaults - option_defaults_ = parent_->option_defaults_; - - // INHERITABLE - failure_message_ = parent_->failure_message_; - allow_extras_ = parent_->allow_extras_; - allow_config_extras_ = parent_->allow_config_extras_; - prefix_command_ = parent_->prefix_command_; - immediate_callback_ = parent_->immediate_callback_; - ignore_case_ = parent_->ignore_case_; - ignore_underscore_ = parent_->ignore_underscore_; - fallthrough_ = parent_->fallthrough_; - validate_positionals_ = parent_->validate_positionals_; - configurable_ = parent_->configurable_; - allow_windows_style_options_ = parent_->allow_windows_style_options_; - group_ = parent_->group_; - footer_ = parent_->footer_; - formatter_ = parent_->formatter_; - config_formatter_ = parent_->config_formatter_; - require_subcommand_max_ = parent_->require_subcommand_max_; - } - } - - public: - /// \name Basic - ///\{ - - /// Create a new program. Pass in the same arguments as main(), along with a help string. - explicit App( std::string app_description = "", std::string app_name = "" ) : - App( app_description, app_name, nullptr ) { - set_help_flag( "-h,--help", "Print this help message and exit" ); - } - - App( const App& ) = delete; - App& operator=( const App& ) = delete; - - /// virtual destructor - virtual ~App() = default; - - /// Set a callback for execution when all parsing and processing has completed - /// - /// Due to a bug in c++11, - /// it is not possible to overload on std::function (fixed in c++14 - /// and backported to c++11 on newer compilers). Use capture by reference - /// to get a pointer to App if needed. - App* callback( std::function app_callback ) { - if ( immediate_callback_ ) { parse_complete_callback_ = std::move( app_callback ); } - else { final_callback_ = std::move( app_callback ); } - return this; - } - - /// Set a callback for execution when all parsing and processing has completed - /// aliased as callback - App* final_callback( std::function app_callback ) { - final_callback_ = std::move( app_callback ); - return this; - } - - /// Set a callback to execute when parsing has completed for the app - /// - App* parse_complete_callback( std::function pc_callback ) { - parse_complete_callback_ = std::move( pc_callback ); - return this; - } - - /// Set a callback to execute prior to parsing. - /// - App* preparse_callback( std::function pp_callback ) { - pre_parse_callback_ = std::move( pp_callback ); - return this; - } - - /// Set a name for the app (empty will use parser to set the name) - App* name( std::string app_name = "" ) { - - if ( parent_ != nullptr ) { - auto oname = name_; - name_ = app_name; - auto& res = _compare_subcommand_names( *this, *_get_fallthrough_parent() ); - if ( !res.empty() ) { - name_ = oname; - throw( - OptionAlreadyAdded( app_name + " conflicts with existing subcommand names" ) ); - } - } - else { name_ = app_name; } - has_automatic_name_ = false; - return this; - } - - /// Set an alias for the app - App* alias( std::string app_name ) { - if ( !detail::valid_name_string( app_name ) ) { - throw( IncorrectConstruction( "alias is not a valid name string" ) ); - } - - if ( parent_ != nullptr ) { - aliases_.push_back( app_name ); - auto& res = _compare_subcommand_names( *this, *_get_fallthrough_parent() ); - if ( !res.empty() ) { - aliases_.pop_back(); - throw( OptionAlreadyAdded( "alias already matches an existing subcommand: " + - app_name ) ); - } - } - else { aliases_.push_back( app_name ); } - - return this; - } - - /// Remove the error when extras are left over on the command line. - App* allow_extras( bool allow = true ) { - allow_extras_ = allow; - return this; - } - - /// Remove the error when extras are left over on the command line. - App* required( bool require = true ) { - required_ = require; - return this; - } - - /// Disable the subcommand or option group - App* disabled( bool disable = true ) { - disabled_ = disable; - return this; - } - - /// silence the subcommand from showing up in the processed list - App* silent( bool silence = true ) { - silent_ = silence; - return this; - } - - /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it - /// is disabled - App* disabled_by_default( bool disable = true ) { - if ( disable ) { default_startup = startup_mode::disabled; } - else { - default_startup = ( default_startup == startup_mode::enabled ) ? startup_mode::enabled - : startup_mode::stable; - } - return this; - } - - /// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is - /// enabled (not disabled) - App* enabled_by_default( bool enable = true ) { - if ( enable ) { default_startup = startup_mode::enabled; } - else { - default_startup = ( default_startup == startup_mode::disabled ) ? startup_mode::disabled - : startup_mode::stable; - } - return this; - } - - /// Set the subcommand callback to be executed immediately on subcommand completion - App* immediate_callback( bool immediate = true ) { - immediate_callback_ = immediate; - if ( immediate_callback_ ) { - if ( final_callback_ && !( parse_complete_callback_ ) ) { - std::swap( final_callback_, parse_complete_callback_ ); - } - } - else if ( !( final_callback_ ) && parse_complete_callback_ ) { - std::swap( final_callback_, parse_complete_callback_ ); - } - return this; - } - - /// Set the subcommand to validate positional arguments before assigning - App* validate_positionals( bool validate = true ) { - validate_positionals_ = validate; - return this; - } - - /// ignore extras in config files - App* allow_config_extras( bool allow = true ) { - if ( allow ) { - allow_config_extras_ = config_extras_mode::capture; - allow_extras_ = true; - } - else { allow_config_extras_ = config_extras_mode::error; } - return this; - } - - /// ignore extras in config files - App* allow_config_extras( config_extras_mode mode ) { - allow_config_extras_ = mode; - return this; - } - - /// Do not parse anything after the first unrecognized option and return - App* prefix_command( bool allow = true ) { - prefix_command_ = allow; - return this; - } - - /// Ignore case. Subcommands inherit value. - App* ignore_case( bool value = true ) { - if ( value && !ignore_case_ ) { - ignore_case_ = true; - auto* p = ( parent_ != nullptr ) ? _get_fallthrough_parent() : this; - auto& match = _compare_subcommand_names( *this, *p ); - if ( !match.empty() ) { - ignore_case_ = false; // we are throwing so need to be exception invariant - throw OptionAlreadyAdded( "ignore case would cause subcommand name conflicts: " + - match ); - } - } - ignore_case_ = value; - return this; - } - - /// Allow windows style options, such as `/opt`. First matching short or long name used. - /// Subcommands inherit value. - App* allow_windows_style_options( bool value = true ) { - allow_windows_style_options_ = value; - return this; - } - - /// Specify that the positional arguments are only at the end of the sequence - App* positionals_at_end( bool value = true ) { - positionals_at_end_ = value; - return this; - } - - /// Specify that the subcommand can be triggered by a config file - App* configurable( bool value = true ) { - configurable_ = value; - return this; - } - - /// Ignore underscore. Subcommands inherit value. - App* ignore_underscore( bool value = true ) { - if ( value && !ignore_underscore_ ) { - ignore_underscore_ = true; - auto* p = ( parent_ != nullptr ) ? _get_fallthrough_parent() : this; - auto& match = _compare_subcommand_names( *this, *p ); - if ( !match.empty() ) { - ignore_underscore_ = false; - throw OptionAlreadyAdded( - "ignore underscore would cause subcommand name conflicts: " + match ); - } - } - ignore_underscore_ = value; - return this; - } - - /// Set the help formatter - App* formatter( std::shared_ptr fmt ) { - formatter_ = fmt; - return this; - } - - /// Set the help formatter - App* formatter_fn( std::function fmt ) { - formatter_ = std::make_shared( fmt ); - return this; - } - - /// Set the config formatter - App* config_formatter( std::shared_ptr fmt ) { - config_formatter_ = fmt; - return this; - } - - /// Check to see if this subcommand was parsed, true only if received on command line. - bool parsed() const { return parsed_ > 0; } - - /// Get the OptionDefault object, to set option defaults - OptionDefaults* option_defaults() { return &option_defaults_; } - - ///\} - /// \name Adding options - ///\{ - - /// Add an option, will automatically understand the type for common types. - /// - /// To use, create a variable with the expected type, and pass it in after the name. - /// After start is called, you can use count to see if the value was passed, and - /// the value will be initialized properly. Numbers, vectors, and strings are supported. - /// - /// ->required(), ->default, and the validators are options, - /// The positional options take an optional number of arguments. - /// - /// For example, - /// - /// std::string filename; - /// program.add_option("filename", filename, "description of filename"); - /// - Option* add_option( std::string option_name, - callback_t option_callback, - std::string option_description = "", - bool defaulted = false, - std::function func = {} ) { - Option myopt { option_name, option_description, option_callback, this }; - - if ( std::find_if( - std::begin( options_ ), std::end( options_ ), [&myopt]( const Option_p& v ) { - return *v == myopt; - } ) == std::end( options_ ) ) { - options_.emplace_back(); - Option_p& option = options_.back(); - option.reset( new Option( option_name, option_description, option_callback, this ) ); - - // Set the default string capture function - option->default_function( func ); - - // For compatibility with CLI11 1.7 and before, capture the default string here - if ( defaulted ) option->capture_default_str(); - - // Transfer defaults to the new option - option_defaults_.copy_to( option.get() ); - - // Don't bother to capture if we already did - if ( !defaulted && option->get_always_capture_default() ) option->capture_default_str(); - - return option.get(); - } - // we know something matches now find what it is so we can produce more error information - for ( auto& opt : options_ ) { - auto& matchname = opt->matching_name( myopt ); - if ( !matchname.empty() ) { - throw( OptionAlreadyAdded( "added option matched existing option name: " + - matchname ) ); - } - } - // this line should not be reached the above loop should trigger the throw - throw( - OptionAlreadyAdded( "added option matched existing option name" ) ); // LCOV_EXCL_LINE - } - - /// Add option for assigning to a variable - template ::value, detail::enabler> = detail::dummy> - Option* add_option( std::string option_name, - AssignTo& variable, ///< The variable to set - std::string option_description = "", - bool defaulted = false ) { - - auto fun = [&variable]( const CLI::results_t& res ) { // comment for spacing - return detail::lexical_conversion( res, variable ); - }; - - Option* opt = add_option( option_name, fun, option_description, defaulted, [&variable]() { - return CLI::detail::checked_to_string( variable ); - } ); - opt->type_name( detail::type_name() ); - // these must be actual lvalues since (std::max) sometimes is defined in terms of references - // and references to structs used in the evaluation can be temporary so that would cause - // issues. - auto Tcount = detail::type_count::value; - auto XCcount = detail::type_count::value; - opt->type_size( detail::type_count_min::value, (std::max)( Tcount, XCcount ) ); - opt->expected( detail::expected_count::value ); - opt->run_callback_for_default(); - return opt; - } - - /// Add option for assigning to a variable - template ::value, detail::enabler> = detail::dummy> - Option* add_option_no_stream( std::string option_name, - AssignTo& variable, ///< The variable to set - std::string option_description = "" ) { - - auto fun = [&variable]( const CLI::results_t& res ) { // comment for spacing - return detail::lexical_conversion( res, variable ); - }; - - Option* opt = add_option( - option_name, fun, option_description, false, []() { return std::string {}; } ); - opt->type_name( detail::type_name() ); - opt->type_size( detail::type_count_min::value, - detail::type_count::value ); - opt->expected( detail::expected_count::value ); - opt->run_callback_for_default(); - return opt; - } - - /// Add option for a callback of a specific type - template - Option* add_option_function( - std::string option_name, - const std::function& func, ///< the callback to execute - std::string option_description = "" ) { - - auto fun = [func]( const CLI::results_t& res ) { - ArgType variable; - bool result = detail::lexical_conversion( res, variable ); - if ( result ) { func( variable ); } - return result; - }; - - Option* opt = add_option( option_name, std::move( fun ), option_description, false ); - opt->type_name( detail::type_name() ); - opt->type_size( detail::type_count_min::value, - detail::type_count::value ); - opt->expected( detail::expected_count::value ); - return opt; - } - - /// Add option with no description or variable assignment - Option* add_option( std::string option_name ) { - return add_option( option_name, CLI::callback_t {}, std::string {}, false ); - } - - /// Add option with description but with no variable assignment or callback - template ::value && std::is_constructible::value, - detail::enabler> = detail::dummy> - Option* add_option( std::string option_name, T& option_description ) { - return add_option( option_name, CLI::callback_t(), option_description, false ); - } - - /// Set a help flag, replace the existing one if present - Option* set_help_flag( std::string flag_name = "", const std::string& help_description = "" ) { - // take flag_description by const reference otherwise add_flag tries to assign to - // help_description - if ( help_ptr_ != nullptr ) { - remove_option( help_ptr_ ); - help_ptr_ = nullptr; - } - - // Empty name will simply remove the help flag - if ( !flag_name.empty() ) { - help_ptr_ = add_flag( flag_name, help_description ); - help_ptr_->configurable( false ); - } - - return help_ptr_; - } - - /// Set a help all flag, replaced the existing one if present - Option* set_help_all_flag( std::string help_name = "", - const std::string& help_description = "" ) { - // take flag_description by const reference otherwise add_flag tries to assign to - // flag_description - if ( help_all_ptr_ != nullptr ) { - remove_option( help_all_ptr_ ); - help_all_ptr_ = nullptr; - } - - // Empty name will simply remove the help all flag - if ( !help_name.empty() ) { - help_all_ptr_ = add_flag( help_name, help_description ); - help_all_ptr_->configurable( false ); - } - - return help_all_ptr_; - } - - /// Set a version flag and version display string, replace the existing one if present - Option* set_version_flag( std::string flag_name = "", const std::string& versionString = "" ) { - // take flag_description by const reference otherwise add_flag tries to assign to - // version_description - if ( version_ptr_ != nullptr ) { - remove_option( version_ptr_ ); - version_ptr_ = nullptr; - } - - // Empty name will simply remove the version flag - if ( !flag_name.empty() ) { - version_ptr_ = add_flag_callback( - flag_name, - [versionString]() { throw( CLI::CallForVersion( versionString, 0 ) ); }, - "Display program version information and exit" ); - version_ptr_->configurable( false ); - } - - return version_ptr_; - } - /// Generate the version string through a callback function - Option* set_version_flag( std::string flag_name, std::function vfunc ) { - // take flag_description by const reference otherwise add_flag tries to assign to - // version_description - if ( version_ptr_ != nullptr ) { - remove_option( version_ptr_ ); - version_ptr_ = nullptr; - } - - // Empty name will simply remove the version flag - if ( !flag_name.empty() ) { - version_ptr_ = add_flag_callback( - flag_name, - [vfunc]() { throw( CLI::CallForVersion( vfunc(), 0 ) ); }, - "Display program version information and exit" ); - version_ptr_->configurable( false ); - } - - return version_ptr_; - } - - private: - /// Internal function for adding a flag - Option* - _add_flag_internal( std::string flag_name, CLI::callback_t fun, std::string flag_description ) { - Option* opt; - if ( detail::has_default_flag_values( flag_name ) ) { - // check for default values and if it has them - auto flag_defaults = detail::get_default_flag_values( flag_name ); - detail::remove_default_flag_values( flag_name ); - opt = add_option( - std::move( flag_name ), std::move( fun ), std::move( flag_description ), false ); - for ( const auto& fname : flag_defaults ) - opt->fnames_.push_back( fname.first ); - opt->default_flag_values_ = std::move( flag_defaults ); - } - else { - opt = add_option( - std::move( flag_name ), std::move( fun ), std::move( flag_description ), false ); - } - // flags cannot have positional values - if ( opt->get_positional() ) { - auto pos_name = opt->get_name( true ); - remove_option( opt ); - throw IncorrectConstruction::PositionalFlag( pos_name ); - } - opt->multi_option_policy( MultiOptionPolicy::TakeLast ); - opt->expected( 0 ); - opt->required( false ); - return opt; - } - - public: - /// Add a flag with no description or variable assignment - Option* add_flag( std::string flag_name ) { - return _add_flag_internal( flag_name, CLI::callback_t(), std::string {} ); - } - - /// Add flag with description but with no variable assignment or callback - /// takes a constant string, if a variable string is passed that variable will be assigned the - /// results from the flag - template ::value && std::is_constructible::value, - detail::enabler> = detail::dummy> - Option* add_flag( std::string flag_name, T& flag_description ) { - return _add_flag_internal( flag_name, CLI::callback_t(), flag_description ); - } - - /// Add option for flag with integer result - defaults to allowing multiple passings, but can be - /// forced to one if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. - template ::value && !is_bool::value, - detail::enabler> = detail::dummy> - Option* add_flag( std::string flag_name, - T& flag_count, ///< A variable holding the count - std::string flag_description = "" ) { - flag_count = 0; - CLI::callback_t fun = [&flag_count]( const CLI::results_t& res ) { - try { - detail::sum_flag_vector( res, flag_count ); - } - catch ( const std::invalid_argument& ) { - return false; - } - return true; - }; - return _add_flag_internal( flag_name, std::move( fun ), std::move( flag_description ) ) - ->multi_option_policy( MultiOptionPolicy::TakeAll ); - } - - /// Other type version accepts all other types that are not vectors such as bool, enum, string - /// or other classes that can be converted from a string - template < - typename T, - enable_if_t::value && !std::is_const::value && - ( !std::is_constructible::value || is_bool::value ) && - !std::is_constructible, T>::value, - detail::enabler> = detail::dummy> - Option* add_flag( std::string flag_name, - T& flag_result, ///< A variable holding true if passed - std::string flag_description = "" ) { - - CLI::callback_t fun = [&flag_result]( const CLI::results_t& res ) { - return CLI::detail::lexical_cast( res[0], flag_result ); - }; - return _add_flag_internal( flag_name, std::move( fun ), std::move( flag_description ) ) - ->run_callback_for_default(); - } - - /// Vector version to capture multiple flags. - template &, T>::value, - detail::enabler> = detail::dummy> - Option* add_flag( std::string flag_name, - std::vector& flag_results, ///< A vector of values with the flag results - std::string flag_description = "" ) { - CLI::callback_t fun = [&flag_results]( const CLI::results_t& res ) { - bool retval = true; - for ( const auto& elem : res ) { - flag_results.emplace_back(); - retval &= detail::lexical_cast( elem, flag_results.back() ); - } - return retval; - }; - return _add_flag_internal( flag_name, std::move( fun ), std::move( flag_description ) ) - ->multi_option_policy( MultiOptionPolicy::TakeAll ) - ->run_callback_for_default(); - } - - /// Add option for callback that is triggered with a true flag and takes no arguments - Option* - add_flag_callback( std::string flag_name, - std::function function, ///< A function to call, void(void) - std::string flag_description = "" ) { - - CLI::callback_t fun = [function]( const CLI::results_t& res ) { - bool trigger { false }; - auto result = CLI::detail::lexical_cast( res[0], trigger ); - if ( result && trigger ) { function(); } - return result; - }; - return _add_flag_internal( flag_name, std::move( fun ), std::move( flag_description ) ); - } - - /// Add option for callback with an integer value - Option* add_flag_function( - std::string flag_name, - std::function function, ///< A function to call, void(int) - std::string flag_description = "" ) { - - CLI::callback_t fun = [function]( const CLI::results_t& res ) { - std::int64_t flag_count = 0; - detail::sum_flag_vector( res, flag_count ); - function( flag_count ); - return true; - }; - return _add_flag_internal( flag_name, std::move( fun ), std::move( flag_description ) ) - ->multi_option_policy( MultiOptionPolicy::TakeAll ); - } - -#ifdef CLI11_CPP14 - /// Add option for callback (C++14 or better only) - Option* add_flag( - std::string flag_name, - std::function function, ///< A function to call, void(std::int64_t) - std::string flag_description = "" ) { - return add_flag_function( - std::move( flag_name ), std::move( function ), std::move( flag_description ) ); - } -#endif - - /// Add a complex number DEPRECATED --use add_option instead - template - Option* add_complex( std::string option_name, - T& variable, - std::string option_description = "", - bool defaulted = false, - std::string label = "COMPLEX" ) { - - CLI::callback_t fun = [&variable]( const results_t& res ) { - XC x, y; - bool worked; - if ( res.size() >= 2 && !res[1].empty() ) { - auto str1 = res[1]; - if ( str1.back() == 'i' || str1.back() == 'j' ) str1.pop_back(); - worked = detail::lexical_cast( res[0], x ) && detail::lexical_cast( str1, y ); - } - else { - auto str1 = res.front(); - auto nloc = str1.find_last_of( '-' ); - if ( nloc != std::string::npos && nloc > 0 ) { - worked = detail::lexical_cast( str1.substr( 0, nloc ), x ); - str1 = str1.substr( nloc ); - if ( str1.back() == 'i' || str1.back() == 'j' ) str1.pop_back(); - worked = worked && detail::lexical_cast( str1, y ); - } - else { - if ( str1.back() == 'i' || str1.back() == 'j' ) { - str1.pop_back(); - worked = detail::lexical_cast( str1, y ); - x = XC { 0 }; - } - else { - worked = detail::lexical_cast( str1, x ); - y = XC { 0 }; - } - } - } - if ( worked ) variable = T { x, y }; - return worked; - }; - - auto default_function = [&variable]() { - return CLI::detail::checked_to_string( variable ); - }; - - CLI::Option* opt = add_option( option_name, - std::move( fun ), - std::move( option_description ), - defaulted, - default_function ); - - opt->type_name( label )->type_size( 1, 2 )->delimiter( '+' )->run_callback_for_default(); - return opt; - } - - /// Set a configuration ini file option, or clear it if no name passed - Option* set_config( std::string option_name = "", - std::string default_filename = "", - const std::string& help_message = "Read an ini file", - bool config_required = false ) { - - // Remove existing config if present - if ( config_ptr_ != nullptr ) { - remove_option( config_ptr_ ); - config_ptr_ = nullptr; // need to remove the config_ptr completely - } - - // Only add config if option passed - if ( !option_name.empty() ) { - config_ptr_ = add_option( option_name, help_message ); - if ( config_required ) { config_ptr_->required(); } - if ( !default_filename.empty() ) { - config_ptr_->default_str( std::move( default_filename ) ); - } - config_ptr_->configurable( false ); - } - - return config_ptr_; - } - - /// Removes an option from the App. Takes an option pointer. Returns true if found and removed. - bool remove_option( Option* opt ) { - // Make sure no links exist - for ( Option_p& op : options_ ) { - op->remove_needs( opt ); - op->remove_excludes( opt ); - } - - if ( help_ptr_ == opt ) help_ptr_ = nullptr; - if ( help_all_ptr_ == opt ) help_all_ptr_ = nullptr; - - auto iterator = std::find_if( std::begin( options_ ), - std::end( options_ ), - [opt]( const Option_p& v ) { return v.get() == opt; } ); - if ( iterator != std::end( options_ ) ) { - options_.erase( iterator ); - return true; - } - return false; - } - - /// creates an option group as part of the given app - template - T* add_option_group( std::string group_name, std::string group_description = "" ) { - auto option_group = std::make_shared( std::move( group_description ), group_name, this ); - auto ptr = option_group.get(); - // move to App_p for overload resolution on older gcc versions - App_p app_ptr = std::dynamic_pointer_cast( option_group ); - add_subcommand( std::move( app_ptr ) ); - return ptr; - } - - ///\} - /// \name Subcommands - ///\{ - - /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag - App* add_subcommand( std::string subcommand_name = "", - std::string subcommand_description = "" ) { - if ( !subcommand_name.empty() && !detail::valid_name_string( subcommand_name ) ) { - throw IncorrectConstruction( "subcommand name is not valid" ); - } - CLI::App_p subcom = std::shared_ptr( - new App( std::move( subcommand_description ), subcommand_name, this ) ); - return add_subcommand( std::move( subcom ) ); - } - - /// Add a previously created app as a subcommand - App* add_subcommand( CLI::App_p subcom ) { - if ( !subcom ) throw IncorrectConstruction( "passed App is not valid" ); - auto ckapp = ( name_.empty() && parent_ != nullptr ) ? _get_fallthrough_parent() : this; - auto& mstrg = _compare_subcommand_names( *subcom, *ckapp ); - if ( !mstrg.empty() ) { - throw( OptionAlreadyAdded( "subcommand name or alias matches existing subcommand: " + - mstrg ) ); - } - subcom->parent_ = this; - subcommands_.push_back( std::move( subcom ) ); - return subcommands_.back().get(); - } - - /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and - /// removed. - bool remove_subcommand( App* subcom ) { - // Make sure no links exist - for ( App_p& sub : subcommands_ ) { - sub->remove_excludes( subcom ); - sub->remove_needs( subcom ); - } - - auto iterator = std::find_if( std::begin( subcommands_ ), - std::end( subcommands_ ), - [subcom]( const App_p& v ) { return v.get() == subcom; } ); - if ( iterator != std::end( subcommands_ ) ) { - subcommands_.erase( iterator ); - return true; - } - return false; - } - /// Check to see if a subcommand is part of this command (doesn't have to be in command line) - /// returns the first subcommand if passed a nullptr - App* get_subcommand( const App* subcom ) const { - if ( subcom == nullptr ) throw OptionNotFound( "nullptr passed" ); - for ( const App_p& subcomptr : subcommands_ ) - if ( subcomptr.get() == subcom ) return subcomptr.get(); - throw OptionNotFound( subcom->get_name() ); - } - - /// Check to see if a subcommand is part of this command (text version) - App* get_subcommand( std::string subcom ) const { - auto subc = _find_subcommand( subcom, false, false ); - if ( subc == nullptr ) throw OptionNotFound( subcom ); - return subc; - } - /// Get a pointer to subcommand by index - App* get_subcommand( int index = 0 ) const { - if ( index >= 0 ) { - auto uindex = static_cast( index ); - if ( uindex < subcommands_.size() ) return subcommands_[uindex].get(); - } - throw OptionNotFound( std::to_string( index ) ); - } - - /// Check to see if a subcommand is part of this command and get a shared_ptr to it - CLI::App_p get_subcommand_ptr( App* subcom ) const { - if ( subcom == nullptr ) throw OptionNotFound( "nullptr passed" ); - for ( const App_p& subcomptr : subcommands_ ) - if ( subcomptr.get() == subcom ) return subcomptr; - throw OptionNotFound( subcom->get_name() ); - } - - /// Check to see if a subcommand is part of this command (text version) - CLI::App_p get_subcommand_ptr( std::string subcom ) const { - for ( const App_p& subcomptr : subcommands_ ) - if ( subcomptr->check_name( subcom ) ) return subcomptr; - throw OptionNotFound( subcom ); - } - - /// Get an owning pointer to subcommand by index - CLI::App_p get_subcommand_ptr( int index = 0 ) const { - if ( index >= 0 ) { - auto uindex = static_cast( index ); - if ( uindex < subcommands_.size() ) return subcommands_[uindex]; - } - throw OptionNotFound( std::to_string( index ) ); - } - - /// Check to see if an option group is part of this App - App* get_option_group( std::string group_name ) const { - for ( const App_p& app : subcommands_ ) { - if ( app->name_.empty() && app->group_ == group_name ) { return app.get(); } - } - throw OptionNotFound( group_name ); - } - - /// No argument version of count counts the number of times this subcommand was - /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless - /// otherwise modified in a callback - std::size_t count() const { return parsed_; } - - /// Get a count of all the arguments processed in options and subcommands, this excludes - /// arguments which were treated as extras. - std::size_t count_all() const { - std::size_t cnt { 0 }; - for ( auto& opt : options_ ) { - cnt += opt->count(); - } - for ( auto& sub : subcommands_ ) { - cnt += sub->count_all(); - } - if ( !get_name().empty() ) { // for named subcommands add the number of times the subcommand - // was called - cnt += parsed_; - } - return cnt; - } - - /// Changes the group membership - App* group( std::string group_name ) { - group_ = group_name; - return this; - } - - /// The argumentless form of require subcommand requires 1 or more subcommands - App* require_subcommand() { - require_subcommand_min_ = 1; - require_subcommand_max_ = 0; - return this; - } - - /// Require a subcommand to be given (does not affect help call) - /// The number required can be given. Negative values indicate maximum - /// number allowed (0 for any number). Max number inheritable. - App* require_subcommand( int value ) { - if ( value < 0 ) { - require_subcommand_min_ = 0; - require_subcommand_max_ = static_cast( -value ); - } - else { - require_subcommand_min_ = static_cast( value ); - require_subcommand_max_ = static_cast( value ); - } - return this; - } - - /// Explicitly control the number of subcommands required. Setting 0 - /// for the max means unlimited number allowed. Max number inheritable. - App* require_subcommand( std::size_t min, std::size_t max ) { - require_subcommand_min_ = min; - require_subcommand_max_ = max; - return this; - } - - /// The argumentless form of require option requires 1 or more options be used - App* require_option() { - require_option_min_ = 1; - require_option_max_ = 0; - return this; - } - - /// Require an option to be given (does not affect help call) - /// The number required can be given. Negative values indicate maximum - /// number allowed (0 for any number). - App* require_option( int value ) { - if ( value < 0 ) { - require_option_min_ = 0; - require_option_max_ = static_cast( -value ); - } - else { - require_option_min_ = static_cast( value ); - require_option_max_ = static_cast( value ); - } - return this; - } - - /// Explicitly control the number of options required. Setting 0 - /// for the max means unlimited number allowed. Max number inheritable. - App* require_option( std::size_t min, std::size_t max ) { - require_option_min_ = min; - require_option_max_ = max; - return this; - } - - /// Stop subcommand fallthrough, so that parent commands cannot collect commands after - /// subcommand. Default from parent, usually set on parent. - App* fallthrough( bool value = true ) { - fallthrough_ = value; - return this; - } - - /// Check to see if this subcommand was parsed, true only if received on command line. - /// This allows the subcommand to be directly checked. - explicit operator bool() const { return parsed_ > 0; } - - ///\} - /// \name Extras for subclassing - ///\{ - - /// This allows subclasses to inject code before callbacks but after parse. - /// - /// This does not run if any errors or help is thrown. - virtual void pre_callback() {} - - ///\} - /// \name Parsing - ///\{ - // - /// Reset the parsed data - void clear() { - - parsed_ = 0; - pre_parse_called_ = false; - - missing_.clear(); - parsed_subcommands_.clear(); - for ( const Option_p& opt : options_ ) { - opt->clear(); - } - for ( const App_p& subc : subcommands_ ) { - subc->clear(); - } - } - - /// Parses the command line - throws errors. - /// This must be called after the options are in but before the rest of the program. - void parse( int argc, const char* const* argv ) { - // If the name is not set, read from command line - if ( name_.empty() || has_automatic_name_ ) { - has_automatic_name_ = true; - name_ = argv[0]; - } - - std::vector args; - args.reserve( static_cast( argc ) - 1 ); - for ( int i = argc - 1; i > 0; i-- ) - args.emplace_back( argv[i] ); - parse( std::move( args ) ); - } - - /// Parse a single string as if it contained command line arguments. - /// This function splits the string into arguments then calls parse(std::vector &) - /// the function takes an optional boolean argument specifying if the programName is included in - /// the string to process - void parse( std::string commandline, bool program_name_included = false ) { - - if ( program_name_included ) { - auto nstr = detail::split_program_name( commandline ); - if ( ( name_.empty() ) || ( has_automatic_name_ ) ) { - has_automatic_name_ = true; - name_ = nstr.first; - } - commandline = std::move( nstr.second ); - } - else { detail::trim( commandline ); } - // the next section of code is to deal with quoted arguments after an '=' or ':' for windows - // like operations - if ( !commandline.empty() ) { - commandline = detail::find_and_modify( commandline, "=", detail::escape_detect ); - if ( allow_windows_style_options_ ) - commandline = detail::find_and_modify( commandline, ":", detail::escape_detect ); - } - - auto args = detail::split_up( std::move( commandline ) ); - // remove all empty strings - args.erase( std::remove( args.begin(), args.end(), std::string {} ), args.end() ); - std::reverse( args.begin(), args.end() ); - - parse( std::move( args ) ); - } - - /// The real work is done here. Expects a reversed vector. - /// Changes the vector to the remaining options. - void parse( std::vector& args ) { - // Clear if parsed - if ( parsed_ > 0 ) clear(); - - // parsed_ is incremented in commands/subcommands, - // but placed here to make sure this is cleared when - // running parse after an error is thrown, even by _validate or _configure. - parsed_ = 1; - _validate(); - _configure(); - // set the parent as nullptr as this object should be the top now - parent_ = nullptr; - parsed_ = 0; - - _parse( args ); - run_callback(); - } - - /// The real work is done here. Expects a reversed vector. - void parse( std::vector&& args ) { - // Clear if parsed - if ( parsed_ > 0 ) clear(); - - // parsed_ is incremented in commands/subcommands, - // but placed here to make sure this is cleared when - // running parse after an error is thrown, even by _validate or _configure. - parsed_ = 1; - _validate(); - _configure(); - // set the parent as nullptr as this object should be the top now - parent_ = nullptr; - parsed_ = 0; - - _parse( std::move( args ) ); - run_callback(); - } - - /// Provide a function to print a help message. The function gets access to the App pointer and - /// error. - void failure_message( std::function function ) { - failure_message_ = function; - } - - /// Print a nice error message and return the exit code - int exit( const Error& e, std::ostream& out = std::cout, std::ostream& err = std::cerr ) const { - - /// Avoid printing anything if this is a CLI::RuntimeError - if ( e.get_name() == "RuntimeError" ) return e.get_exit_code(); - - if ( e.get_name() == "CallForHelp" ) { - out << help(); - return e.get_exit_code(); - } - - if ( e.get_name() == "CallForAllHelp" ) { - out << help( "", AppFormatMode::All ); - return e.get_exit_code(); - } - - if ( e.get_name() == "CallForVersion" ) { - out << e.what() << std::endl; - return e.get_exit_code(); - } - - if ( e.get_exit_code() != static_cast( ExitCodes::Success ) ) { - if ( failure_message_ ) err << failure_message_( this, e ) << std::flush; - } - - return e.get_exit_code(); - } - - ///\} - /// \name Post parsing - ///\{ - - /// Counts the number of times the given option was passed. - std::size_t count( std::string option_name ) const { - return get_option( option_name )->count(); - } - - /// Get a subcommand pointer list to the currently selected subcommands (after parsing by - /// default, in command line order; use parsed = false to get the original definition list.) - std::vector get_subcommands() const { return parsed_subcommands_; } - - /// Get a filtered subcommand pointer list from the original definition list. An empty function - /// will provide all subcommands (const) - std::vector - get_subcommands( const std::function& filter ) const { - std::vector subcomms( subcommands_.size() ); - std::transform( std::begin( subcommands_ ), - std::end( subcommands_ ), - std::begin( subcomms ), - []( const App_p& v ) { return v.get(); } ); - - if ( filter ) { - subcomms.erase( - std::remove_if( std::begin( subcomms ), - std::end( subcomms ), - [&filter]( const App* app ) { return !filter( app ); } ), - std::end( subcomms ) ); - } - - return subcomms; - } - - /// Get a filtered subcommand pointer list from the original definition list. An empty function - /// will provide all subcommands - std::vector get_subcommands( const std::function& filter ) { - std::vector subcomms( subcommands_.size() ); - std::transform( std::begin( subcommands_ ), - std::end( subcommands_ ), - std::begin( subcomms ), - []( const App_p& v ) { return v.get(); } ); - - if ( filter ) { - subcomms.erase( std::remove_if( std::begin( subcomms ), - std::end( subcomms ), - [&filter]( App* app ) { return !filter( app ); } ), - std::end( subcomms ) ); - } - - return subcomms; - } - - /// Check to see if given subcommand was selected - bool got_subcommand( const App* subcom ) const { - // get subcom needed to verify that this was a real subcommand - return get_subcommand( subcom )->parsed_ > 0; - } - - /// Check with name instead of pointer to see if subcommand was selected - bool got_subcommand( std::string subcommand_name ) const { - return get_subcommand( subcommand_name )->parsed_ > 0; - } - - /// Sets excluded options for the subcommand - App* excludes( Option* opt ) { - if ( opt == nullptr ) { throw OptionNotFound( "nullptr passed" ); } - exclude_options_.insert( opt ); - return this; - } - - /// Sets excluded subcommands for the subcommand - App* excludes( App* app ) { - if ( app == nullptr ) { throw OptionNotFound( "nullptr passed" ); } - if ( app == this ) { throw OptionNotFound( "cannot self reference in needs" ); } - auto res = exclude_subcommands_.insert( app ); - // subcommand exclusion should be symmetric - if ( res.second ) { app->exclude_subcommands_.insert( this ); } - return this; - } - - App* needs( Option* opt ) { - if ( opt == nullptr ) { throw OptionNotFound( "nullptr passed" ); } - need_options_.insert( opt ); - return this; - } - - App* needs( App* app ) { - if ( app == nullptr ) { throw OptionNotFound( "nullptr passed" ); } - if ( app == this ) { throw OptionNotFound( "cannot self reference in needs" ); } - need_subcommands_.insert( app ); - return this; - } - - /// Removes an option from the excludes list of this subcommand - bool remove_excludes( Option* opt ) { - auto iterator = - std::find( std::begin( exclude_options_ ), std::end( exclude_options_ ), opt ); - if ( iterator == std::end( exclude_options_ ) ) { return false; } - exclude_options_.erase( iterator ); - return true; - } - - /// Removes a subcommand from the excludes list of this subcommand - bool remove_excludes( App* app ) { - auto iterator = - std::find( std::begin( exclude_subcommands_ ), std::end( exclude_subcommands_ ), app ); - if ( iterator == std::end( exclude_subcommands_ ) ) { return false; } - auto other_app = *iterator; - exclude_subcommands_.erase( iterator ); - other_app->remove_excludes( this ); - return true; - } - - /// Removes an option from the needs list of this subcommand - bool remove_needs( Option* opt ) { - auto iterator = std::find( std::begin( need_options_ ), std::end( need_options_ ), opt ); - if ( iterator == std::end( need_options_ ) ) { return false; } - need_options_.erase( iterator ); - return true; - } - - /// Removes a subcommand from the needs list of this subcommand - bool remove_needs( App* app ) { - auto iterator = - std::find( std::begin( need_subcommands_ ), std::end( need_subcommands_ ), app ); - if ( iterator == std::end( need_subcommands_ ) ) { return false; } - need_subcommands_.erase( iterator ); - return true; - } - - ///\} - /// \name Help - ///\{ - - /// Set footer. - App* footer( std::string footer_string ) { - footer_ = std::move( footer_string ); - return this; - } - /// Set footer. - App* footer( std::function footer_function ) { - footer_callback_ = std::move( footer_function ); - return this; - } - /// Produce a string that could be read in as a config of the current values of the App. Set - /// default_also to include default arguments. write_descriptions will print a description for - /// the App and for each option. - std::string config_to_str( bool default_also = false, bool write_description = false ) const { - return config_formatter_->to_config( this, default_also, write_description, "" ); - } - - /// Makes a help message, using the currently configured formatter - /// Will only do one subcommand at a time - std::string help( std::string prev = "", AppFormatMode mode = AppFormatMode::Normal ) const { - if ( prev.empty() ) - prev = get_name(); - else - prev += " " + get_name(); - - // Delegate to subcommand if needed - auto selected_subcommands = get_subcommands(); - if ( !selected_subcommands.empty() ) { - return selected_subcommands.at( 0 )->help( prev, mode ); - } - return formatter_->make_help( this, prev, mode ); - } - - /// Displays a version string - std::string version() const { - std::string val; - if ( version_ptr_ != nullptr ) { - auto rv = version_ptr_->results(); - version_ptr_->clear(); - version_ptr_->add_result( "true" ); - try { - version_ptr_->run_callback(); - } - catch ( const CLI::CallForVersion& cfv ) { - val = cfv.what(); - } - version_ptr_->clear(); - version_ptr_->add_result( rv ); - } - return val; - } - ///\} - /// \name Getters - ///\{ - - /// Access the formatter - std::shared_ptr get_formatter() const { return formatter_; } - - /// Access the config formatter - std::shared_ptr get_config_formatter() const { return config_formatter_; } - - /// Access the config formatter as a configBase pointer - std::shared_ptr get_config_formatter_base() const { - // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase -#if defined( __cpp_rtti ) || ( defined( __GXX_RTTI ) && __GXX_RTTI ) || \ - ( defined( _HAS_STATIC_RTTI ) && ( _HAS_STATIC_RTTI == 0 ) ) - return std::dynamic_pointer_cast( config_formatter_ ); -#else - return std::static_pointer_cast( config_formatter_ ); -#endif - } - - /// Get the app or subcommand description - std::string get_description() const { return description_; } - - /// Set the description of the app - App* description( std::string app_description ) { - description_ = std::move( app_description ); - return this; - } - - /// Get the list of options (user facing function, so returns raw pointers), has optional filter - /// function - std::vector - get_options( const std::function filter = {} ) const { - std::vector options( options_.size() ); - std::transform( std::begin( options_ ), - std::end( options_ ), - std::begin( options ), - []( const Option_p& val ) { return val.get(); } ); - - if ( filter ) { - options.erase( - std::remove_if( std::begin( options ), - std::end( options ), - [&filter]( const Option* opt ) { return !filter( opt ); } ), - std::end( options ) ); - } - - return options; - } - - /// Non-const version of the above - std::vector get_options( const std::function filter = {} ) { - std::vector options( options_.size() ); - std::transform( std::begin( options_ ), - std::end( options_ ), - std::begin( options ), - []( const Option_p& val ) { return val.get(); } ); - - if ( filter ) { - options.erase( std::remove_if( std::begin( options ), - std::end( options ), - [&filter]( Option* opt ) { return !filter( opt ); } ), - std::end( options ) ); - } - - return options; - } - - /// Get an option by name (noexcept non-const version) - Option* get_option_no_throw( std::string option_name ) noexcept { - for ( Option_p& opt : options_ ) { - if ( opt->check_name( option_name ) ) { return opt.get(); } - } - for ( auto& subc : subcommands_ ) { - // also check down into nameless subcommands - if ( subc->get_name().empty() ) { - auto opt = subc->get_option_no_throw( option_name ); - if ( opt != nullptr ) { return opt; } - } - } - return nullptr; - } - - /// Get an option by name (noexcept const version) - const Option* get_option_no_throw( std::string option_name ) const noexcept { - for ( const Option_p& opt : options_ ) { - if ( opt->check_name( option_name ) ) { return opt.get(); } - } - for ( const auto& subc : subcommands_ ) { - // also check down into nameless subcommands - if ( subc->get_name().empty() ) { - auto opt = subc->get_option_no_throw( option_name ); - if ( opt != nullptr ) { return opt; } - } - } - return nullptr; - } - - /// Get an option by name - const Option* get_option( std::string option_name ) const { - auto opt = get_option_no_throw( option_name ); - if ( opt == nullptr ) { throw OptionNotFound( option_name ); } - return opt; - } - - /// Get an option by name (non-const version) - Option* get_option( std::string option_name ) { - auto opt = get_option_no_throw( option_name ); - if ( opt == nullptr ) { throw OptionNotFound( option_name ); } - return opt; - } - - /// Shortcut bracket operator for getting a pointer to an option - const Option* operator[]( const std::string& option_name ) const { - return get_option( option_name ); - } - - /// Shortcut bracket operator for getting a pointer to an option - const Option* operator[]( const char* option_name ) const { return get_option( option_name ); } - - /// Check the status of ignore_case - bool get_ignore_case() const { return ignore_case_; } - - /// Check the status of ignore_underscore - bool get_ignore_underscore() const { return ignore_underscore_; } - - /// Check the status of fallthrough - bool get_fallthrough() const { return fallthrough_; } - - /// Check the status of the allow windows style options - bool get_allow_windows_style_options() const { return allow_windows_style_options_; } - - /// Check the status of the allow windows style options - bool get_positionals_at_end() const { return positionals_at_end_; } - - /// Check the status of the allow windows style options - bool get_configurable() const { return configurable_; } - - /// Get the group of this subcommand - const std::string& get_group() const { return group_; } - - /// Generate and return the footer. - std::string get_footer() const { - return ( footer_callback_ ) ? footer_callback_() + '\n' + footer_ : footer_; - } - - /// Get the required min subcommand value - std::size_t get_require_subcommand_min() const { return require_subcommand_min_; } - - /// Get the required max subcommand value - std::size_t get_require_subcommand_max() const { return require_subcommand_max_; } - - /// Get the required min option value - std::size_t get_require_option_min() const { return require_option_min_; } - - /// Get the required max option value - std::size_t get_require_option_max() const { return require_option_max_; } - - /// Get the prefix command status - bool get_prefix_command() const { return prefix_command_; } - - /// Get the status of allow extras - bool get_allow_extras() const { return allow_extras_; } - - /// Get the status of required - bool get_required() const { return required_; } - - /// Get the status of disabled - bool get_disabled() const { return disabled_; } - - /// Get the status of silence - bool get_silent() const { return silent_; } - - /// Get the status of disabled - bool get_immediate_callback() const { return immediate_callback_; } - - /// Get the status of disabled by default - bool get_disabled_by_default() const { return ( default_startup == startup_mode::disabled ); } - - /// Get the status of disabled by default - bool get_enabled_by_default() const { return ( default_startup == startup_mode::enabled ); } - /// Get the status of validating positionals - bool get_validate_positionals() const { return validate_positionals_; } - - /// Get the status of allow extras - config_extras_mode get_allow_config_extras() const { return allow_config_extras_; } - - /// Get a pointer to the help flag. - Option* get_help_ptr() { return help_ptr_; } - - /// Get a pointer to the help flag. (const) - const Option* get_help_ptr() const { return help_ptr_; } - - /// Get a pointer to the help all flag. (const) - const Option* get_help_all_ptr() const { return help_all_ptr_; } - - /// Get a pointer to the config option. - Option* get_config_ptr() { return config_ptr_; } - - /// Get a pointer to the config option. (const) - const Option* get_config_ptr() const { return config_ptr_; } - - /// Get a pointer to the version option. - Option* get_version_ptr() { return version_ptr_; } - - /// Get a pointer to the version option. (const) - const Option* get_version_ptr() const { return version_ptr_; } - - /// Get the parent of this subcommand (or nullptr if master app) - App* get_parent() { return parent_; } - - /// Get the parent of this subcommand (or nullptr if master app) (const version) - const App* get_parent() const { return parent_; } - - /// Get the name of the current app - const std::string& get_name() const { return name_; } - - /// Get the aliases of the current app - const std::vector& get_aliases() const { return aliases_; } - - /// clear all the aliases of the current App - App* clear_aliases() { - aliases_.clear(); - return this; - } - - /// Get a display name for an app - std::string get_display_name( bool with_aliases = false ) const { - if ( name_.empty() ) { return std::string( "[Option Group: " ) + get_group() + "]"; } - if ( aliases_.empty() || !with_aliases || aliases_.empty() ) { return name_; } - std::string dispname = name_; - for ( const auto& lalias : aliases_ ) { - dispname.push_back( ',' ); - dispname.push_back( ' ' ); - dispname.append( lalias ); - } - return dispname; - } - - /// Check the name, case insensitive and underscore insensitive if set - bool check_name( std::string name_to_check ) const { - std::string local_name = name_; - if ( ignore_underscore_ ) { - local_name = detail::remove_underscore( name_ ); - name_to_check = detail::remove_underscore( name_to_check ); - } - if ( ignore_case_ ) { - local_name = detail::to_lower( name_ ); - name_to_check = detail::to_lower( name_to_check ); - } - - if ( local_name == name_to_check ) { return true; } - for ( auto les : aliases_ ) { - if ( ignore_underscore_ ) { les = detail::remove_underscore( les ); } - if ( ignore_case_ ) { les = detail::to_lower( les ); } - if ( les == name_to_check ) { return true; } - } - return false; - } - - /// Get the groups available directly from this option (in order) - std::vector get_groups() const { - std::vector groups; - - for ( const Option_p& opt : options_ ) { - // Add group if it is not already in there - if ( std::find( groups.begin(), groups.end(), opt->get_group() ) == groups.end() ) { - groups.push_back( opt->get_group() ); - } - } - - return groups; - } - - /// This gets a vector of pointers with the original parse order - const std::vector& parse_order() const { return parse_order_; } - - /// This returns the missing options from the current subcommand - std::vector remaining( bool recurse = false ) const { - std::vector miss_list; - for ( const std::pair& miss : missing_ ) { - miss_list.push_back( std::get<1>( miss ) ); - } - // Get from a subcommand that may allow extras - if ( recurse ) { - if ( !allow_extras_ ) { - for ( const auto& sub : subcommands_ ) { - if ( sub->name_.empty() && !sub->missing_.empty() ) { - for ( const std::pair& miss : - sub->missing_ ) { - miss_list.push_back( std::get<1>( miss ) ); - } - } - } - } - // Recurse into subcommands - - for ( const App* sub : parsed_subcommands_ ) { - std::vector output = sub->remaining( recurse ); - std::copy( - std::begin( output ), std::end( output ), std::back_inserter( miss_list ) ); - } - } - return miss_list; - } - - /// This returns the missing options in a form ready for processing by another command line - /// program - std::vector remaining_for_passthrough( bool recurse = false ) const { - std::vector miss_list = remaining( recurse ); - std::reverse( std::begin( miss_list ), std::end( miss_list ) ); - return miss_list; - } - - /// This returns the number of remaining options, minus the -- separator - std::size_t remaining_size( bool recurse = false ) const { - auto remaining_options = static_cast( - std::count_if( std::begin( missing_ ), - std::end( missing_ ), - []( const std::pair& val ) { - return val.first != detail::Classifier::POSITIONAL_MARK; - } ) ); - - if ( recurse ) { - for ( const App_p& sub : subcommands_ ) { - remaining_options += sub->remaining_size( recurse ); - } - } - return remaining_options; - } - - ///\} - - protected: - /// Check the options to make sure there are no conflicts. - /// - /// Currently checks to see if multiple positionals exist with unlimited args and checks if the - /// min and max options are feasible - void _validate() const { - // count the number of positional only args - auto pcount = - std::count_if( std::begin( options_ ), std::end( options_ ), []( const Option_p& opt ) { - return opt->get_items_expected_max() >= detail::expected_max_vector_size && - !opt->nonpositional(); - } ); - if ( pcount > 1 ) { - auto pcount_req = std::count_if( - std::begin( options_ ), std::end( options_ ), []( const Option_p& opt ) { - return opt->get_items_expected_max() >= detail::expected_max_vector_size && - !opt->nonpositional() && opt->get_required(); - } ); - if ( pcount - pcount_req > 1 ) { throw InvalidError( name_ ); } - } - - std::size_t nameless_subs { 0 }; - for ( const App_p& app : subcommands_ ) { - app->_validate(); - if ( app->get_name().empty() ) ++nameless_subs; - } - - if ( require_option_min_ > 0 ) { - if ( require_option_max_ > 0 ) { - if ( require_option_max_ < require_option_min_ ) { - throw( InvalidError( "Required min options greater than required max options", - ExitCodes::InvalidError ) ); - } - } - if ( require_option_min_ > ( options_.size() + nameless_subs ) ) { - throw( - InvalidError( "Required min options greater than number of available options", - ExitCodes::InvalidError ) ); - } - } - } - - /// configure subcommands to enable parsing through the current object - /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic - /// enable or disable makes sure parent is set correctly - void _configure() { - if ( default_startup == startup_mode::enabled ) { disabled_ = false; } - else if ( default_startup == startup_mode::disabled ) { disabled_ = true; } - for ( const App_p& app : subcommands_ ) { - if ( app->has_automatic_name_ ) { app->name_.clear(); } - if ( app->name_.empty() ) { - app->fallthrough_ = - false; // make sure fallthrough_ is false to prevent infinite loop - app->prefix_command_ = false; - } - // make sure the parent is set to be this object in preparation for parse - app->parent_ = this; - app->_configure(); - } - } - /// Internal function to run (App) callback, bottom up - void run_callback( bool final_mode = false ) { - pre_callback(); - // in the main app if immediate_callback_ is set it runs the main callback before the used - // subcommands - if ( !final_mode && parse_complete_callback_ ) { parse_complete_callback_(); } - // run the callbacks for the received subcommands - for ( App* subc : get_subcommands() ) { - subc->run_callback( true ); - } - // now run callbacks for option_groups - for ( auto& subc : subcommands_ ) { - if ( subc->name_.empty() && subc->count_all() > 0 ) { subc->run_callback( true ); } - } - - // finally run the main callback - if ( final_callback_ && ( parsed_ > 0 ) ) { - if ( !name_.empty() || count_all() > 0 ) { final_callback_(); } - } - } - - /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been - /// reached. - bool _valid_subcommand( const std::string& current, bool ignore_used = true ) const { - // Don't match if max has been reached - but still check parents - if ( require_subcommand_max_ != 0 && - parsed_subcommands_.size() >= require_subcommand_max_ ) { - return parent_ != nullptr && parent_->_valid_subcommand( current, ignore_used ); - } - auto com = _find_subcommand( current, true, ignore_used ); - if ( com != nullptr ) { return true; } - // Check parent if exists, else return false - return parent_ != nullptr && parent_->_valid_subcommand( current, ignore_used ); - } - - /// Selects a Classifier enum based on the type of the current argument - detail::Classifier _recognize( const std::string& current, - bool ignore_used_subcommands = true ) const { - std::string dummy1, dummy2; - - if ( current == "--" ) return detail::Classifier::POSITIONAL_MARK; - if ( _valid_subcommand( current, ignore_used_subcommands ) ) - return detail::Classifier::SUBCOMMAND; - if ( detail::split_long( current, dummy1, dummy2 ) ) return detail::Classifier::LONG; - if ( detail::split_short( current, dummy1, dummy2 ) ) { - if ( dummy1[0] >= '0' && dummy1[0] <= '9' ) { - if ( get_option_no_throw( std::string { '-', dummy1[0] } ) == nullptr ) { - return detail::Classifier::NONE; - } - } - return detail::Classifier::SHORT; - } - if ( ( allow_windows_style_options_ ) && - ( detail::split_windows_style( current, dummy1, dummy2 ) ) ) - return detail::Classifier::WINDOWS_STYLE; - if ( ( current == "++" ) && !name_.empty() && parent_ != nullptr ) - return detail::Classifier::SUBCOMMAND_TERMINATOR; - return detail::Classifier::NONE; - } - - // The parse function is now broken into several parts, and part of process - - /// Read and process a configuration file (main app only) - void _process_config_file() { - if ( config_ptr_ != nullptr ) { - bool config_required = config_ptr_->get_required(); - auto file_given = config_ptr_->count() > 0; - auto config_files = config_ptr_->as>(); - if ( config_files.empty() || config_files.front().empty() ) { - if ( config_required ) { throw FileError::Missing( "no specified config file" ); } - return; - } - for ( auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit ) { - const auto& config_file = *rit; - auto path_result = detail::check_path( config_file.c_str() ); - if ( path_result == detail::path_type::file ) { - try { - std::vector values = - config_formatter_->from_file( config_file ); - _parse_config( values ); - if ( !file_given ) { config_ptr_->add_result( config_file ); } - } - catch ( const FileError& ) { - if ( config_required || file_given ) throw; - } - } - else if ( config_required || file_given ) { - throw FileError::Missing( config_file ); - } - } - } - } - - /// Get envname options if not yet passed. Runs on *all* subcommands. - void _process_env() { - for ( const Option_p& opt : options_ ) { - if ( opt->count() == 0 && !opt->envname_.empty() ) { - char* buffer = nullptr; - std::string ename_string; - -#ifdef _MSC_VER - // Windows version - std::size_t sz = 0; - if ( _dupenv_s( &buffer, &sz, opt->envname_.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( opt->envname_.c_str() ); - if ( buffer != nullptr ) ename_string = std::string( buffer ); -#endif - - if ( !ename_string.empty() ) { opt->add_result( ename_string ); } - } - } - - for ( App_p& sub : subcommands_ ) { - if ( sub->get_name().empty() || !sub->parse_complete_callback_ ) sub->_process_env(); - } - } - - /// Process callbacks. Runs on *all* subcommands. - void _process_callbacks() { - - for ( App_p& sub : subcommands_ ) { - // process the priority option_groups first - if ( sub->get_name().empty() && sub->parse_complete_callback_ ) { - if ( sub->count_all() > 0 ) { - sub->_process_callbacks(); - sub->run_callback(); - } - } - } - - for ( const Option_p& opt : options_ ) { - if ( opt->count() > 0 && !opt->get_callback_run() ) { opt->run_callback(); } - } - for ( App_p& sub : subcommands_ ) { - if ( !sub->parse_complete_callback_ ) { sub->_process_callbacks(); } - } - } - - /// Run help flag processing if any are found. - /// - /// The flags allow recursive calls to remember if there was a help flag on a parent. - void _process_help_flags( bool trigger_help = false, bool trigger_all_help = false ) const { - const Option* help_ptr = get_help_ptr(); - const Option* help_all_ptr = get_help_all_ptr(); - - if ( help_ptr != nullptr && help_ptr->count() > 0 ) trigger_help = true; - if ( help_all_ptr != nullptr && help_all_ptr->count() > 0 ) trigger_all_help = true; - - // If there were parsed subcommands, call those. First subcommand wins if there are multiple - // ones. - if ( !parsed_subcommands_.empty() ) { - for ( const App* sub : parsed_subcommands_ ) - sub->_process_help_flags( trigger_help, trigger_all_help ); - - // Only the final subcommand should call for help. All help wins over help. - } - else if ( trigger_all_help ) { throw CallForAllHelp(); } - else if ( trigger_help ) { throw CallForHelp(); } - } - - /// Verify required options and cross requirements. Subcommands too (only if selected). - void _process_requirements() { - // check excludes - bool excluded { false }; - std::string excluder; - for ( auto& opt : exclude_options_ ) { - if ( opt->count() > 0 ) { - excluded = true; - excluder = opt->get_name(); - } - } - for ( auto& subc : exclude_subcommands_ ) { - if ( subc->count_all() > 0 ) { - excluded = true; - excluder = subc->get_display_name(); - } - } - if ( excluded ) { - if ( count_all() > 0 ) { throw ExcludesError( get_display_name(), excluder ); } - // if we are excluded but didn't receive anything, just return - return; - } - - // check excludes - bool missing_needed { false }; - std::string missing_need; - for ( auto& opt : need_options_ ) { - if ( opt->count() == 0 ) { - missing_needed = true; - missing_need = opt->get_name(); - } - } - for ( auto& subc : need_subcommands_ ) { - if ( subc->count_all() == 0 ) { - missing_needed = true; - missing_need = subc->get_display_name(); - } - } - if ( missing_needed ) { - if ( count_all() > 0 ) { throw RequiresError( get_display_name(), missing_need ); } - // if we missing something but didn't have any options, just return - return; - } - - std::size_t used_options = 0; - for ( const Option_p& opt : options_ ) { - - if ( opt->count() != 0 ) { ++used_options; } - // Required but empty - if ( opt->get_required() && opt->count() == 0 ) { - throw RequiredError( opt->get_name() ); - } - // Requires - for ( const Option* opt_req : opt->needs_ ) - if ( opt->count() > 0 && opt_req->count() == 0 ) - throw RequiresError( opt->get_name(), opt_req->get_name() ); - // Excludes - for ( const Option* opt_ex : opt->excludes_ ) - if ( opt->count() > 0 && opt_ex->count() != 0 ) - throw ExcludesError( opt->get_name(), opt_ex->get_name() ); - } - // check for the required number of subcommands - if ( require_subcommand_min_ > 0 ) { - auto selected_subcommands = get_subcommands(); - if ( require_subcommand_min_ > selected_subcommands.size() ) - throw RequiredError::Subcommand( require_subcommand_min_ ); - } - - // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining - // item. - - // run this loop to check how many unnamed subcommands were actually used since they are - // considered options from the perspective of an App - for ( App_p& sub : subcommands_ ) { - if ( sub->disabled_ ) continue; - if ( sub->name_.empty() && sub->count_all() > 0 ) { ++used_options; } - } - - if ( require_option_min_ > used_options || - ( require_option_max_ > 0 && require_option_max_ < used_options ) ) { - auto option_list = detail::join( options_, [this]( const Option_p& ptr ) { - if ( ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_ ) { - return std::string {}; - } - return ptr->get_name( false, true ); - } ); - - auto subc_list = get_subcommands( - []( App* app ) { return ( ( app->get_name().empty() ) && ( !app->disabled_ ) ); } ); - if ( !subc_list.empty() ) { - option_list += "," + detail::join( subc_list, []( const App* app ) { - return app->get_display_name(); - } ); - } - throw RequiredError::Option( - require_option_min_, require_option_max_, used_options, option_list ); - } - - // now process the requirements for subcommands if needed - for ( App_p& sub : subcommands_ ) { - if ( sub->disabled_ ) continue; - if ( sub->name_.empty() && sub->required_ == false ) { - if ( sub->count_all() == 0 ) { - if ( require_option_min_ > 0 && require_option_min_ <= used_options ) { - continue; - // if we have met the requirement and there is nothing in this option group - // skip checking requirements - } - if ( require_option_max_ > 0 && used_options >= require_option_min_ ) { - continue; - // if we have met the requirement and there is nothing in this option group - // skip checking requirements - } - } - } - if ( sub->count() > 0 || sub->name_.empty() ) { sub->_process_requirements(); } - - if ( sub->required_ && sub->count_all() == 0 ) { - throw( CLI::RequiredError( sub->get_display_name() ) ); - } - } - } - - /// Process callbacks and such. - void _process() { - _process_config_file(); - _process_env(); - _process_callbacks(); - _process_help_flags(); - _process_requirements(); - } - - /// Throw an error if anything is left over and should not be. - void _process_extras() { - if ( !( allow_extras_ || prefix_command_ ) ) { - std::size_t num_left_over = remaining_size(); - if ( num_left_over > 0 ) { throw ExtrasError( name_, remaining( false ) ); } - } - - for ( App_p& sub : subcommands_ ) { - if ( sub->count() > 0 ) sub->_process_extras(); - } - } - - /// Throw an error if anything is left over and should not be. - /// Modifies the args to fill in the missing items before throwing. - void _process_extras( std::vector& args ) { - if ( !( allow_extras_ || prefix_command_ ) ) { - std::size_t num_left_over = remaining_size(); - if ( num_left_over > 0 ) { - args = remaining( false ); - throw ExtrasError( name_, args ); - } - } - - for ( App_p& sub : subcommands_ ) { - if ( sub->count() > 0 ) sub->_process_extras( args ); - } - } - - /// Internal function to recursively increment the parsed counter on the current app as well - /// unnamed subcommands - void increment_parsed() { - ++parsed_; - for ( App_p& sub : subcommands_ ) { - if ( sub->get_name().empty() ) sub->increment_parsed(); - } - } - /// Internal parse function - void _parse( std::vector& args ) { - increment_parsed(); - _trigger_pre_parse( args.size() ); - bool positional_only = false; - - while ( !args.empty() ) { - if ( !_parse_single( args, positional_only ) ) { break; } - } - - if ( parent_ == nullptr ) { - _process(); - - // Throw error if any items are left over (depending on settings) - _process_extras( args ); - - // Convert missing (pairs) to extras (string only) ready for processing in another app - args = remaining_for_passthrough( false ); - } - else if ( parse_complete_callback_ ) { - _process_env(); - _process_callbacks(); - _process_help_flags(); - _process_requirements(); - run_callback(); - } - } - - /// Internal parse function - void _parse( std::vector&& args ) { - // this can only be called by the top level in which case parent == nullptr by definition - // operation is simplified - increment_parsed(); - _trigger_pre_parse( args.size() ); - bool positional_only = false; - - while ( !args.empty() ) { - _parse_single( args, positional_only ); - } - _process(); - - // Throw error if any items are left over (depending on settings) - _process_extras(); - } - - /// Parse one config param, return false if not found in any subcommand, remove if it is - /// - /// If this has more than one dot.separated.name, go into the subcommand matching it - /// Returns true if it managed to find the option, if false you'll need to remove the arg - /// manually. - void _parse_config( const std::vector& args ) { - for ( const ConfigItem& item : args ) { - if ( !_parse_single_config( item ) && - allow_config_extras_ == config_extras_mode::error ) - throw ConfigError::Extras( item.fullname() ); - } - } - - /// Fill in a single config option - bool _parse_single_config( const ConfigItem& item, std::size_t level = 0 ) { - if ( level < item.parents.size() ) { - try { - auto subcom = get_subcommand( item.parents.at( level ) ); - auto result = subcom->_parse_single_config( item, level + 1 ); - - return result; - } - catch ( const OptionNotFound& ) { - return false; - } - } - // check for section open - if ( item.name == "++" ) { - if ( configurable_ ) { - increment_parsed(); - _trigger_pre_parse( 2 ); - if ( parent_ != nullptr ) { parent_->parsed_subcommands_.push_back( this ); } - } - return true; - } - // check for section close - if ( item.name == "--" ) { - if ( configurable_ ) { - _process_callbacks(); - _process_requirements(); - run_callback(); - } - return true; - } - Option* op = get_option_no_throw( "--" + item.name ); - if ( op == nullptr ) { - if ( item.name.size() == 1 ) { op = get_option_no_throw( "-" + item.name ); } - } - if ( op == nullptr ) { op = get_option_no_throw( item.name ); } - if ( op == nullptr ) { - // If the option was not present - if ( get_allow_config_extras() == config_extras_mode::capture ) - // Should we worry about classifying the extras properly? - missing_.emplace_back( detail::Classifier::NONE, item.fullname() ); - return false; - } - - if ( !op->get_configurable() ) throw ConfigError::NotConfigurable( item.fullname() ); - - if ( op->empty() ) { - // Flag parsing - if ( op->get_expected_min() == 0 ) { - auto res = config_formatter_->to_flag( item ); - res = op->get_flag_value( item.name, res ); - - op->add_result( res ); - } - else { - op->add_result( item.inputs ); - op->run_callback(); - } - } - - return true; - } - - /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to - /// missing if missing from master return false if the parse has failed and needs to return to - /// parent - bool _parse_single( std::vector& args, bool& positional_only ) { - bool retval = true; - detail::Classifier classifier = - positional_only ? detail::Classifier::NONE : _recognize( args.back() ); - switch ( classifier ) { - case detail::Classifier::POSITIONAL_MARK: - args.pop_back(); - positional_only = true; - if ( ( !_has_remaining_positionals() ) && ( parent_ != nullptr ) ) { retval = false; } - else { _move_to_missing( classifier, "--" ); } - break; - case detail::Classifier::SUBCOMMAND_TERMINATOR: - // treat this like a positional mark if in the parent app - args.pop_back(); - retval = false; - break; - case detail::Classifier::SUBCOMMAND: - retval = _parse_subcommand( args ); - break; - case detail::Classifier::LONG: - case detail::Classifier::SHORT: - case detail::Classifier::WINDOWS_STYLE: - // If already parsed a subcommand, don't accept options_ - _parse_arg( args, classifier ); - break; - case detail::Classifier::NONE: - // Probably a positional or something for a parent (sub)command - retval = _parse_positional( args, false ); - if ( retval && positionals_at_end_ ) { positional_only = true; } - break; - // LCOV_EXCL_START - default: - throw HorribleError( "unrecognized classifier (you should not see this!)" ); - // LCOV_EXCL_STOP - } - return retval; - } - - /// Count the required remaining positional arguments - std::size_t _count_remaining_positionals( bool required_only = false ) const { - std::size_t retval = 0; - for ( const Option_p& opt : options_ ) { - if ( opt->get_positional() && ( !required_only || opt->get_required() ) ) { - if ( opt->get_items_expected_min() > 0 && - static_cast( opt->count() ) < opt->get_items_expected_min() ) { - retval += - static_cast( opt->get_items_expected_min() ) - opt->count(); - } - } - } - return retval; - } - - /// Count the required remaining positional arguments - bool _has_remaining_positionals() const { - for ( const Option_p& opt : options_ ) { - if ( opt->get_positional() && - ( ( static_cast( opt->count() ) < opt->get_items_expected_min() ) ) ) { - return true; - } - } - - return false; - } - - /// Parse a positional, go up the tree to check - /// \param haltOnSubcommand if set to true the operation will not process subcommands merely - /// return false Return true if the positional was used false otherwise - bool _parse_positional( std::vector& args, bool haltOnSubcommand ) { - - const std::string& positional = args.back(); - - if ( positionals_at_end_ ) { - // deal with the case of required arguments at the end which should take precedence over - // other arguments - auto arg_rem = args.size(); - auto remreq = _count_remaining_positionals( true ); - if ( arg_rem <= remreq ) { - for ( const Option_p& opt : options_ ) { - if ( opt->get_positional() && opt->required_ ) { - if ( static_cast( opt->count() ) < opt->get_items_expected_min() ) { - if ( validate_positionals_ ) { - std::string pos = positional; - pos = opt->_validate( pos, 0 ); - if ( !pos.empty() ) { continue; } - } - opt->add_result( positional ); - parse_order_.push_back( opt.get() ); - args.pop_back(); - return true; - } - } - } - } - } - for ( const Option_p& opt : options_ ) { - // Eat options, one by one, until done - if ( opt->get_positional() && - ( static_cast( opt->count() ) < opt->get_items_expected_min() || - opt->get_allow_extra_args() ) ) { - if ( validate_positionals_ ) { - std::string pos = positional; - pos = opt->_validate( pos, 0 ); - if ( !pos.empty() ) { continue; } - } - opt->add_result( positional ); - parse_order_.push_back( opt.get() ); - args.pop_back(); - return true; - } - } - - for ( auto& subc : subcommands_ ) { - if ( ( subc->name_.empty() ) && ( !subc->disabled_ ) ) { - if ( subc->_parse_positional( args, false ) ) { - if ( !subc->pre_parse_called_ ) { subc->_trigger_pre_parse( args.size() ); } - return true; - } - } - } - // let the parent deal with it if possible - if ( parent_ != nullptr && fallthrough_ ) - return _get_fallthrough_parent()->_parse_positional( - args, static_cast( parse_complete_callback_ ) ); - - /// Try to find a local subcommand that is repeated - auto com = _find_subcommand( args.back(), true, false ); - if ( com != nullptr && ( require_subcommand_max_ == 0 || - require_subcommand_max_ > parsed_subcommands_.size() ) ) { - if ( haltOnSubcommand ) { return false; } - args.pop_back(); - com->_parse( args ); - return true; - } - /// now try one last gasp at subcommands that have been executed before, go to root app and - /// try to find a subcommand in a broader way, if one exists let the parent deal with it - auto parent_app = ( parent_ != nullptr ) ? _get_fallthrough_parent() : this; - com = parent_app->_find_subcommand( args.back(), true, false ); - if ( com != nullptr && ( com->parent_->require_subcommand_max_ == 0 || - com->parent_->require_subcommand_max_ > - com->parent_->parsed_subcommands_.size() ) ) { - return false; - } - - if ( positionals_at_end_ ) { throw CLI::ExtrasError( name_, args ); } - /// If this is an option group don't deal with it - if ( parent_ != nullptr && name_.empty() ) { return false; } - /// We are out of other options this goes to missing - _move_to_missing( detail::Classifier::NONE, positional ); - args.pop_back(); - if ( prefix_command_ ) { - while ( !args.empty() ) { - _move_to_missing( detail::Classifier::NONE, args.back() ); - args.pop_back(); - } - } - - return true; - } - - /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and - /// should used subcommands be ignored - App* _find_subcommand( const std::string& subc_name, - bool ignore_disabled, - bool ignore_used ) const noexcept { - for ( const App_p& com : subcommands_ ) { - if ( com->disabled_ && ignore_disabled ) continue; - if ( com->get_name().empty() ) { - auto subc = com->_find_subcommand( subc_name, ignore_disabled, ignore_used ); - if ( subc != nullptr ) { return subc; } - } - if ( com->check_name( subc_name ) ) { - if ( ( !*com ) || !ignore_used ) return com.get(); - } - } - return nullptr; - } - - /// Parse a subcommand, modify args and continue - /// - /// Unlike the others, this one will always allow fallthrough - /// return true if the subcommand was processed false otherwise - bool _parse_subcommand( std::vector& args ) { - if ( _count_remaining_positionals( /* required */ true ) > 0 ) { - _parse_positional( args, false ); - return true; - } - auto com = _find_subcommand( args.back(), true, true ); - if ( com != nullptr ) { - args.pop_back(); - if ( !com->silent_ ) { parsed_subcommands_.push_back( com ); } - com->_parse( args ); - auto parent_app = com->parent_; - while ( parent_app != this ) { - parent_app->_trigger_pre_parse( args.size() ); - if ( !com->silent_ ) { parent_app->parsed_subcommands_.push_back( com ); } - parent_app = parent_app->parent_; - } - return true; - } - - if ( parent_ == nullptr ) throw HorribleError( "Subcommand " + args.back() + " missing" ); - return false; - } - - /// Parse a short (false) or long (true) argument, must be at the top of the list - /// return true if the argument was processed or false if nothing was done - bool _parse_arg( std::vector& args, detail::Classifier current_type ) { - - std::string current = args.back(); - - std::string arg_name; - std::string value; - std::string rest; - - switch ( current_type ) { - case detail::Classifier::LONG: - if ( !detail::split_long( current, arg_name, value ) ) - throw HorribleError( "Long parsed but missing (you should not see this):" + - args.back() ); - break; - case detail::Classifier::SHORT: - if ( !detail::split_short( current, arg_name, rest ) ) - throw HorribleError( "Short parsed but missing! You should not see this" ); - break; - case detail::Classifier::WINDOWS_STYLE: - if ( !detail::split_windows_style( current, arg_name, value ) ) - throw HorribleError( "windows option parsed but missing! You should not see this" ); - break; - case detail::Classifier::SUBCOMMAND: - case detail::Classifier::SUBCOMMAND_TERMINATOR: - case detail::Classifier::POSITIONAL_MARK: - case detail::Classifier::NONE: - default: - throw HorribleError( - "parsing got called with invalid option! You should not see this" ); - } - - auto op_ptr = - std::find_if( std::begin( options_ ), - std::end( options_ ), - [arg_name, current_type]( const Option_p& opt ) { - if ( current_type == detail::Classifier::LONG ) - return opt->check_lname( arg_name ); - if ( current_type == detail::Classifier::SHORT ) - return opt->check_sname( arg_name ); - // this will only get called for detail::Classifier::WINDOWS_STYLE - return opt->check_lname( arg_name ) || opt->check_sname( arg_name ); - } ); - - // Option not found - if ( op_ptr == std::end( options_ ) ) { - for ( auto& subc : subcommands_ ) { - if ( subc->name_.empty() && !subc->disabled_ ) { - if ( subc->_parse_arg( args, current_type ) ) { - if ( !subc->pre_parse_called_ ) { subc->_trigger_pre_parse( args.size() ); } - return true; - } - } - } - // If a subcommand, try the master command - if ( parent_ != nullptr && fallthrough_ ) - return _get_fallthrough_parent()->_parse_arg( args, current_type ); - // don't capture missing if this is a nameless subcommand - if ( parent_ != nullptr && name_.empty() ) { return false; } - // Otherwise, add to missing - args.pop_back(); - _move_to_missing( current_type, current ); - return true; - } - - args.pop_back(); - - // Get a reference to the pointer to make syntax bearable - Option_p& op = *op_ptr; - /// if we require a separator add it here - if ( op->get_inject_separator() ) { - if ( !op->results().empty() && !op->results().back().empty() ) { - op->add_result( std::string {} ); - } - } - int min_num = (std::min)( op->get_type_size_min(), op->get_items_expected_min() ); - int max_num = op->get_items_expected_max(); - // check container like options to limit the argument size to a single type if the - // allow_extra_flags argument is set. 16 is somewhat arbitrary (needs to be at least 4) - if ( max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args() ) { - auto tmax = op->get_type_size_max(); - max_num = detail::checked_multiply( tmax, op->get_expected_min() ) - ? tmax - : detail::expected_max_vector_size; - } - // Make sure we always eat the minimum for unlimited vectors - int collected = 0; // total number of arguments collected - int result_count = 0; // local variable for number of results in a single arg string - // deal with purely flag like things - if ( max_num == 0 ) { - auto res = op->get_flag_value( arg_name, value ); - op->add_result( res ); - parse_order_.push_back( op.get() ); - } - else if ( !value.empty() ) { // --this=value - op->add_result( value, result_count ); - parse_order_.push_back( op.get() ); - collected += result_count; - // -Trest - } - else if ( !rest.empty() ) { - op->add_result( rest, result_count ); - parse_order_.push_back( op.get() ); - rest = ""; - collected += result_count; - } - - // gather the minimum number of arguments - while ( min_num > collected && !args.empty() ) { - std::string current_ = args.back(); - args.pop_back(); - op->add_result( current_, result_count ); - parse_order_.push_back( op.get() ); - collected += result_count; - } - - if ( min_num > collected ) { // if we have run out of arguments and the minimum was not met - throw ArgumentMismatch::TypedAtLeast( op->get_name(), min_num, op->get_type_name() ); - } - - if ( max_num > collected || op->get_allow_extra_args() ) { // we allow optional arguments - auto remreqpos = _count_remaining_positionals( true ); - // we have met the minimum now optionally check up to the maximum - while ( ( collected < max_num || op->get_allow_extra_args() ) && !args.empty() && - _recognize( args.back(), false ) == detail::Classifier::NONE ) { - // If any required positionals remain, don't keep eating - if ( remreqpos >= args.size() ) { break; } - - op->add_result( args.back(), result_count ); - parse_order_.push_back( op.get() ); - args.pop_back(); - collected += result_count; - } - - // Allow -- to end an unlimited list and "eat" it - if ( !args.empty() && _recognize( args.back() ) == detail::Classifier::POSITIONAL_MARK ) - args.pop_back(); - // optional flag that didn't receive anything now get the default value - if ( min_num == 0 && max_num > 0 && collected == 0 ) { - auto res = op->get_flag_value( arg_name, std::string {} ); - op->add_result( res ); - parse_order_.push_back( op.get() ); - } - } - - // if we only partially completed a type then add an empty string for later processing - if ( min_num > 0 && op->get_type_size_max() != min_num && - ( collected % op->get_type_size_max() ) != 0 ) { - op->add_result( std::string {} ); - } - - if ( !rest.empty() ) { - rest = "-" + rest; - args.push_back( rest ); - } - return true; - } - - /// Trigger the pre_parse callback if needed - void _trigger_pre_parse( std::size_t remaining_args ) { - if ( !pre_parse_called_ ) { - pre_parse_called_ = true; - if ( pre_parse_callback_ ) { pre_parse_callback_( remaining_args ); } - } - else if ( immediate_callback_ ) { - if ( !name_.empty() ) { - auto pcnt = parsed_; - auto extras = std::move( missing_ ); - clear(); - parsed_ = pcnt; - pre_parse_called_ = true; - missing_ = std::move( extras ); - } - } - } - - /// Get the appropriate parent to fallthrough to which is the first one that has a name or the - /// main app - App* _get_fallthrough_parent() { - if ( parent_ == nullptr ) { throw( HorribleError( "No Valid parent" ) ); } - auto fallthrough_parent = parent_; - while ( ( fallthrough_parent->parent_ != nullptr ) && - ( fallthrough_parent->get_name().empty() ) ) { - fallthrough_parent = fallthrough_parent->parent_; - } - return fallthrough_parent; - } - - /// Helper function to run through all possible comparisons of subcommand names to check there - /// is no overlap - const std::string& _compare_subcommand_names( const App& subcom, const App& base ) const { - static const std::string estring; - if ( subcom.disabled_ ) { return estring; } - for ( auto& subc : base.subcommands_ ) { - if ( subc.get() != &subcom ) { - if ( subc->disabled_ ) { continue; } - if ( !subcom.get_name().empty() ) { - if ( subc->check_name( subcom.get_name() ) ) { return subcom.get_name(); } - } - if ( !subc->get_name().empty() ) { - if ( subcom.check_name( subc->get_name() ) ) { return subc->get_name(); } - } - for ( const auto& les : subcom.aliases_ ) { - if ( subc->check_name( les ) ) { return les; } - } - // this loop is needed in case of ignore_underscore or ignore_case on one but not - // the other - for ( const auto& les : subc->aliases_ ) { - if ( subcom.check_name( les ) ) { return les; } - } - // if the subcommand is an option group we need to check deeper - if ( subc->get_name().empty() ) { - auto& cmpres = _compare_subcommand_names( subcom, *subc ); - if ( !cmpres.empty() ) { return cmpres; } - } - // if the test subcommand is an option group we need to check deeper - if ( subcom.get_name().empty() ) { - auto& cmpres = _compare_subcommand_names( *subc, subcom ); - if ( !cmpres.empty() ) { return cmpres; } - } - } - } - return estring; - } - /// Helper function to place extra values in the most appropriate position - void _move_to_missing( detail::Classifier val_type, const std::string& val ) { - if ( allow_extras_ || subcommands_.empty() ) { - missing_.emplace_back( val_type, val ); - return; - } - // allow extra arguments to be places in an option group if it is allowed there - for ( auto& subc : subcommands_ ) { - if ( subc->name_.empty() && subc->allow_extras_ ) { - subc->missing_.emplace_back( val_type, val ); - return; - } - } - // if we haven't found any place to put them yet put them in missing - missing_.emplace_back( val_type, val ); - } - - public: - /// function that could be used by subclasses of App to shift options around into subcommands - void _move_option( Option* opt, App* app ) { - if ( opt == nullptr ) { throw OptionNotFound( "the option is NULL" ); } - // verify that the give app is actually a subcommand - bool found = false; - for ( auto& subc : subcommands_ ) { - if ( app == subc.get() ) { found = true; } - } - if ( !found ) { throw OptionNotFound( "The Given app is not a subcommand" ); } - - if ( ( help_ptr_ == opt ) || ( help_all_ptr_ == opt ) ) - throw OptionAlreadyAdded( "cannot move help options" ); - - if ( config_ptr_ == opt ) throw OptionAlreadyAdded( "cannot move config file options" ); - - auto iterator = std::find_if( std::begin( options_ ), - std::end( options_ ), - [opt]( const Option_p& v ) { return v.get() == opt; } ); - if ( iterator != std::end( options_ ) ) { - const auto& opt_p = *iterator; - if ( std::find_if( std::begin( app->options_ ), - std::end( app->options_ ), - [&opt_p]( const Option_p& v ) { return ( *v == *opt_p ); } ) == - std::end( app->options_ ) ) { - // only erase after the insertion was successful - app->options_.push_back( std::move( *iterator ) ); - options_.erase( iterator ); - } - else { throw OptionAlreadyAdded( "option was not located: " + opt->get_name() ); } - } - else { throw OptionNotFound( "could not locate the given Option" ); } - } -}; // namespace CLI - -/// Extension of App to better manage groups of options -class Option_group : public App -{ - public: - Option_group( std::string group_description, std::string group_name, App* parent ) : - App( std::move( group_description ), "", parent ) { - group( group_name ); - // option groups should have automatic fallthrough - } - using App::add_option; - /// Add an existing option to the Option_group - Option* add_option( Option* opt ) { - if ( get_parent() == nullptr ) { - throw OptionNotFound( "Unable to locate the specified option" ); - } - get_parent()->_move_option( opt, this ); - return opt; - } - /// Add an existing option to the Option_group - void add_options( Option* opt ) { add_option( opt ); } - /// Add a bunch of options to the group - template - void add_options( Option* opt, Args... args ) { - add_option( opt ); - add_options( args... ); - } - using App::add_subcommand; - /// Add an existing subcommand to be a member of an option_group - App* add_subcommand( App* subcom ) { - App_p subc = subcom->get_parent()->get_subcommand_ptr( subcom ); - subc->get_parent()->remove_subcommand( subcom ); - add_subcommand( std::move( subc ) ); - return subcom; - } -}; -/// Helper function to enable one option group/subcommand when another is used -inline void TriggerOn( App* trigger_app, App* app_to_enable ) { - app_to_enable->enabled_by_default( false ); - app_to_enable->disabled_by_default(); - trigger_app->preparse_callback( - [app_to_enable]( std::size_t ) { app_to_enable->disabled( false ); } ); -} - -/// Helper function to enable one option group/subcommand when another is used -inline void TriggerOn( App* trigger_app, std::vector apps_to_enable ) { - for ( auto& app : apps_to_enable ) { - app->enabled_by_default( false ); - app->disabled_by_default(); - } - - trigger_app->preparse_callback( [apps_to_enable]( std::size_t ) { - for ( auto& app : apps_to_enable ) { - app->disabled( false ); - } - } ); -} - -/// Helper function to disable one option group/subcommand when another is used -inline void TriggerOff( App* trigger_app, App* app_to_enable ) { - app_to_enable->disabled_by_default( false ); - app_to_enable->enabled_by_default(); - trigger_app->preparse_callback( [app_to_enable]( std::size_t ) { app_to_enable->disabled(); } ); -} - -/// Helper function to disable one option group/subcommand when another is used -inline void TriggerOff( App* trigger_app, std::vector apps_to_enable ) { - for ( auto& app : apps_to_enable ) { - app->disabled_by_default( false ); - app->enabled_by_default(); - } - - trigger_app->preparse_callback( [apps_to_enable]( std::size_t ) { - for ( auto& app : apps_to_enable ) { - app->disabled(); - } - } ); -} - -/// Helper function to mark an option as deprecated -inline void deprecate_option( Option* opt, const std::string& replacement = "" ) { - Validator deprecate_warning { [opt, replacement]( std::string& ) { - std::cout << opt->get_name() << " is deprecated please use '" - << replacement << "' instead\n"; - return std::string(); - }, - "DEPRECATED" }; - deprecate_warning.application_index( 0 ); - opt->check( deprecate_warning ); - if ( !replacement.empty() ) { - opt->description( opt->get_description() + " DEPRECATED: please use '" + replacement + - "' instead" ); - } -} - -/// Helper function to mark an option as deprecated -inline void -deprecate_option( App* app, const std::string& option_name, const std::string& replacement = "" ) { - auto opt = app->get_option( option_name ); - deprecate_option( opt, replacement ); -} - -/// Helper function to mark an option as deprecated -inline void -deprecate_option( App& app, const std::string& option_name, const std::string& replacement = "" ) { - auto opt = app.get_option( option_name ); - deprecate_option( opt, replacement ); -} - -/// Helper function to mark an option as retired -inline void retire_option( App* app, Option* opt ) { - App temp; - auto option_copy = temp.add_option( opt->get_name( false, true ) ) - ->type_size( opt->get_type_size_min(), opt->get_type_size_max() ) - ->expected( opt->get_expected_min(), opt->get_expected_max() ) - ->allow_extra_args( opt->get_allow_extra_args() ); - - app->remove_option( opt ); - auto opt2 = - app->add_option( option_copy->get_name( false, true ), - "option has been retired and has no effect" ) - ->type_name( "RETIRED" ) - ->default_str( "RETIRED" ) - ->type_size( option_copy->get_type_size_min(), option_copy->get_type_size_max() ) - ->expected( option_copy->get_expected_min(), option_copy->get_expected_max() ) - ->allow_extra_args( option_copy->get_allow_extra_args() ); - - Validator retired_warning { [opt2]( std::string& ) { - std::cout << "WARNING " << opt2->get_name() - << " is retired and has no effect\n"; - return std::string(); - }, - "" }; - retired_warning.application_index( 0 ); - opt2->check( retired_warning ); -} - -/// Helper function to mark an option as retired -inline void retire_option( App& app, Option* opt ) { - retire_option( &app, opt ); -} - -/// Helper function to mark an option as retired -inline void retire_option( App* app, const std::string& option_name ) { - - auto opt = app->get_option_no_throw( option_name ); - if ( opt != nullptr ) { - retire_option( app, opt ); - return; - } - auto opt2 = app->add_option( option_name, "option has been retired and has no effect" ) - ->type_name( "RETIRED" ) - ->expected( 0, 1 ) - ->default_str( "RETIRED" ); - Validator retired_warning { [opt2]( std::string& ) { - std::cout << "WARNING " << opt2->get_name() - << " is retired and has no effect\n"; - return std::string(); - }, - "" }; - retired_warning.application_index( 0 ); - opt2->check( retired_warning ); -} - -/// Helper function to mark an option as retired -inline void retire_option( App& app, const std::string& option_name ) { - retire_option( &app, option_name ); -} - -namespace FailureMessage { - -/// Printout a clean, simple message on error (the default in CLI11 1.5+) -inline std::string simple( const App* app, const Error& e ) { - std::string header = std::string( e.what() ) + "\n"; - std::vector names; - - // Collect names - if ( app->get_help_ptr() != nullptr ) names.push_back( app->get_help_ptr()->get_name() ); - - if ( app->get_help_all_ptr() != nullptr ) - names.push_back( app->get_help_all_ptr()->get_name() ); - - // If any names found, suggest those - if ( !names.empty() ) - header += "Run with " + detail::join( names, " or " ) + " for more information.\n"; - - return header; -} - -/// Printout the full help string on error (if this fn is set, the old default for CLI11) -inline std::string help( const App* app, const Error& e ) { - std::string header = std::string( "ERROR: " ) + e.get_name() + ": " + e.what() + "\n"; - header += app->help(); - return header; -} - -} // namespace FailureMessage - -namespace detail { -/// This class is simply to allow tests access to App's protected functions -struct AppFriend { -#ifdef CLI11_CPP14 - - /// Wrap _parse_short, perfectly forward arguments and return - template - static decltype( auto ) parse_arg( App* app, Args&&... args ) { - return app->_parse_arg( std::forward( args )... ); - } - - /// Wrap _parse_subcommand, perfectly forward arguments and return - template - static decltype( auto ) parse_subcommand( App* app, Args&&... args ) { - return app->_parse_subcommand( std::forward( args )... ); - } -#else - /// Wrap _parse_short, perfectly forward arguments and return - template - static auto parse_arg( App* app, Args&&... args ) -> - typename std::result_of::type { - return app->_parse_arg( std::forward( args )... ); - } - - /// Wrap _parse_subcommand, perfectly forward arguments and return - template - static auto parse_subcommand( App* app, Args&&... args ) -> - typename std::result_of::type { - return app->_parse_subcommand( std::forward( args )... ); - } -#endif - /// Wrap the fallthrough parent function to make sure that is working correctly - static App* get_fallthrough_parent( App* app ) { return app->_get_fallthrough_parent(); } -}; -} // namespace detail - -// [CLI11:app_hpp:end] -} // namespace CLI diff --git a/src/Headless/CLI/CLI.hpp b/src/Headless/CLI/CLI.hpp deleted file mode 100644 index 98fca54884b..00000000000 --- a/src/Headless/CLI/CLI.hpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner -// under NSF AWARD 1414736 and by the respective contributors. -// All rights reserved. -// -// SPDX-License-Identifier: BSD-3-Clause - -#pragma once - -// CLI Library includes -// Order is important for combiner script - -#include "Version.hpp" - -#include "Macros.hpp" - -#include "StringTools.hpp" - -#include "Error.hpp" - -#include "TypeTools.hpp" - -#include "Split.hpp" - -#include "ConfigFwd.hpp" - -#include "Validators.hpp" - -#include "FormatterFwd.hpp" - -#include "Option.hpp" - -#include "App.hpp" - -#include "Config.hpp" - -#include "Formatter.hpp" diff --git a/src/Headless/CLI/CLI11.hpp b/src/Headless/CLI/CLI11.hpp new file mode 100644 index 00000000000..99382395945 --- /dev/null +++ b/src/Headless/CLI/CLI11.hpp @@ -0,0 +1,12045 @@ +// CLI11: Version 2.6.1 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.6.1 +// +// CLI11 2.6.1 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 6 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "2.6.1" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#if __cplusplus > 202002L +#define CLI11_CPP23 +#if __cplusplus > 202302L +#define CLI11_CPP26 +#endif +#endif +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard was fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#if _MSVC_LANG > 202002L && _MSC_VER >= 1922 +#define CLI11_CPP23 +#endif +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if (defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if (defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif (defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + +/** availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** availability */ +#if !defined(CLI11_CPP26) && !defined(CLI11_HAS_CODECVT) +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif +#else +#if defined(CLI11_HAS_CODECVT) +#if CLI11_HAS_CODECVT > 0 +#include +#endif +#else +#define CLI11_HAS_CODECVT 0 +#endif +#endif + +/** rtti enabled */ +#ifndef CLI11_HAS_RTTI +#if defined(__GXX_RTTI) && __GXX_RTTI == 1 +// gcc +#define CLI11_HAS_RTTI 1 +#elif defined(_CPPRTTI) && _CPPRTTI == 1 +// msvc +#define CLI11_HAS_RTTI 1 +#elif defined(__NO_RTTI__) && __NO_RTTI__ == 1 +// intel +#define CLI11_HAS_RTTI 0 +#elif defined(__has_feature) +// clang and other newer compilers +#if __has_feature(cxx_rtti) +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#elif defined(__RTTI) || defined(__INTEL_RTTI__) +// more intel and some other compilers +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + + +namespace CLI { + + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + // https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number + return in << +static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +CLI11_INLINE std::vector split(const std::string &s, char delim); + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + auto rval = s.str(); + if(!rval.empty() && delim.size() == 1 && rval.back() == delim[0]) { + // remove trailing delimiter if the last entry was empty + rval.pop_back(); + } + return rval; +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +CLI11_INLINE std::string <rim(std::string &str); + +/// Trim anything from left of string +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); + +/// Trim whitespace from right of string +CLI11_INLINE std::string &rtrim(std::string &str); + +/// Trim anything from right of string +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +CLI11_INLINE std::string &remove_quotes(std::string &str); + +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} + +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + return ((str.find_first_of('\n') == std::string::npos) && (str.find_first_of('\0') == std::string::npos)); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + return (str.empty() || (str.size() == 2 && str[0] == '%' && str[1] == '%')); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// @brief get valid group separators _' + local separator if different +/// @return a string containing the group separators +CLI11_INLINE std::string get_group_separators(); + +/// Find and replace a substring with another substring +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags); + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); + +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapable characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force = false); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, + char string_char = '\"', + char literal_char = '\'', + bool disable_secondary_array_processing = false); + +/// This function formats the given text as a paragraph with fixed width and applies correct line wrapping +/// with a custom line prefix. The paragraph will get streamed to the given ostream. +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, + const std::string &text, + std::size_t paragraphWidth, + const std::string &linePrefix = "", + bool skipPrefixOnFirstLine = false); + +} // namespace detail + + + + +namespace detail { +CLI11_INLINE std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +CLI11_INLINE std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find_first_of("\r\n", n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +CLI11_INLINE std::string get_group_separators() { + std::string separators{"_'"}; +#if CLI11_HAS_RTTI != 0 + char group_separator = std::use_facet>(std::locale()).thousands_sep(); + separators.push_back(group_separator); +#endif + return separators; +} + +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +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: +#if defined(_MSC_VER) && _MSC_VER < 1920 + case(std::size_t)-1: +#else + case std::string::npos: +#endif + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + } else if(c == 'x' || c == 'X') { + // need to check for inadvertent binary sequences + if(!escaped_string.empty() && escaped_string.back() == '\\') { + escaped_string += std::string("\\x") + (c == 'x' ? "78" : "58"); + } else { + escaped_string.push_back(c); + } + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape || force) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string[sqLoc] = '\\'; + escaped_string.insert(sqLoc + 1, "x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE void handle_secondary_array(std::string &str) { + if(str.size() >= 2 && str.front() == '[' && str.back() == ']') { + // handle some special array processing for arguments if it might be interpreted as a secondary array + std::string tstr{"[["}; + for(std::size_t ii = 1; ii < str.size(); ++ii) { + tstr.push_back(str[ii]); + tstr.push_back(str[ii]); + } + str = std::move(tstr); + } +} + +CLI11_INLINE bool +process_quoted_string(std::string &str, char string_char, char literal_char, bool disable_secondary_array_processing) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + if(!disable_secondary_array_processing) + handle_secondary_array(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + if(!disable_secondary_array_processing) + handle_secondary_array(str); + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + if(!disable_secondary_array_processing) + handle_secondary_array(str); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + 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 : int { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } + + CLI11_NODISCARD std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } + static BadNameString ReservedName(std::string name) { + return BadNameString("Names '-','--','++' are reserved and not allowed as option names " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return {"Requires at least " + std::to_string(min_option) + " options used but only " + + std::to_string(used) + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } + if(max_option == 1) + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; + + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::join(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::join(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a nonexistent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler : std::uint8_t {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { + using type = void; +}; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { + using type = T; +}; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { + using type = std::string; +}; + +namespace adl_detail { +/// Check for existence of user-supplied lexical_cast. +/// +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). +template class is_lexical_castable { + template + static auto test(int) -> decltype(lexical_cast(std::declval(), std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; +} // namespace adl_detail + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { + using type = T; +}; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { + using type = typename element_type::type::value_type; +}; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a -Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else +#pragma diag_suppress 2361 +#endif +#endif + TT{std::declval()} +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else +#pragma diag_default 2361 +#endif +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, and an end +/// method. +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +// Even though in C++26 std::complex gains a std::tuple interface, for our purposes we treat is as NOT a tuple +template class is_tuple_like { + template ::value, detail::enabler> = detail::dummy> + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{// cppcheck-suppress unusedStructMember + std::tuple_size::type>::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + return std::string(value); // NOLINT(google-readability-casting) +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +// additional forward declarations + +/// Print tuple value string for tuples of size ==1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&value); + +/// Print tuple value string for tuples of size > 1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&value); + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template < + typename T, + enable_if_t::value && !std::is_constructible::value && + !is_ostreamable::value && !is_readable_container::type>::value && + !is_tuple_like::value, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&) { + return {}; +} + +/// convert a readable container to a string +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_readable_container::value && !is_tuple_like::value, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return {"[" + detail::join(defaults) + "]"}; +} + +/// Convert a tuple like object to a string + +/// forward declarations for tuple_value_strings +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/); + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value); + +/// Print tuple value string for tuples of size ==1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value == 1, + detail::enabler>> +inline std::string to_string(T &&value) { + return to_string(std::get<0>(value)); +} + +/// Print tuple value string for tuples of size > 1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler>> +inline std::string to_string(T &&value) { + auto tname = std::string(1, '[') + tuple_value_string(value); + tname.push_back(']'); + return tname; +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/) { + return std::string{}; +} + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value) { + auto str = std::string{to_string(std::get(value))} + ',' + tuple_value_string(value); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::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 : std::uint8_t { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + std::string nstring = input; + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + } + } + return integral_conversion(nstring, output); + } + + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { + // LCOV_EXCL_START + // In some new compilers including the coverage testing one binary strings are handled properly in strtoull + // automatically so this coverage is missing but is well tested in other compilers + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + // LCOV_EXCL_STOP + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + return integral_conversion(nstring, output); + } + } + } + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { + // LCOV_EXCL_START + // In some new compilers including the coverage testing one binary strings are handled properly in strtoll + // automatically so this coverage is missing but is well tested in other compilers + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + // LCOV_EXCL_STOP + } + return false; +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret = 0; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + errno = EINVAL; + return -1; + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + std::int8_t res{0}; + // we do it this way as some systems have char as signed and not, this ensures consistency in the way things are + // handled + bool result = integral_conversion(input, res); + if(result) { + output = static_cast(res); + } + return result; +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { + output = (out > 0); + } else if(errno == ERANGE) { + output = (input[0] != '-'); + } else { + return false; + } + return true; +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + while(std::isspace(static_cast(*val))) { + ++val; + if(val == (input.c_str() + input.size())) { + return true; + } + } + + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + return lexical_cast(nstring, output); + } + } + } + return false; +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val = 0.0; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value && + is_istreamable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return from_stream(input, output); +} + +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a +/// user-supplied lexical_cast overload. +template ::value == object_category::other && !std::is_assignable::value && + !is_istreamable::value && !adl_detail::is_lexical_castable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { + static_assert(!std::is_same::value, // Can't just write false here. + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return false; +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} // LCOV_EXCL_LINE + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2{}; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } + return lexical_assign(strings[0], output); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; +} + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input, bool allow_non_standard = false); + +} // namespace detail + + + +namespace detail { + +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE std::vector split_names(std::string current) { + std::vector output; + std::size_t val = 0; + while((val = current.find(',')) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input, bool allow_non_standard) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + for(std::string name : input) { + if(name.empty()) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) { + short_names.emplace_back(1, name[1]); + } else if(name.length() > 2) { + if(allow_non_standard) { + name = name.substr(1); + if(valid_name_string(name)) { + short_names.push_back(name); + } else { + throw BadNameString::BadLongName(name); + } + } else { + throw BadNameString::MissingDash(name); + } + } else { + throw BadNameString::OneCharName(name); + } + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) { + long_names.push_back(name); + } else { + throw BadNameString::BadLongName(name); + } + } else if(name == "-" || name == "--" || name == "++") { + throw BadNameString::ReservedName(name); + } else { + if(!pos_name.empty()) { + throw BadNameString::MultiPositionalNames(name); + } + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } + } + } + return std::make_tuple(short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + /// Listing of inputs + std::vector inputs{}; + /// @brief indicator if a multiline vector separator was inserted + bool multiline{false}; + /// The list of parents and name joined by "." + CLI11_NODISCARD std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + (void)multiline; // suppression for cppcheck false positive + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + CLI11_NODISCARD std::vector from_file(const std::string &name) const { + 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); +}; + +/// Alias for Validator for custom Validator for clarity +using CustomValidator = Validator; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type : std::uint8_t { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +// Static is not needed here, because global const implies static. + +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator(); +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator(); +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator(); +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // namespace detail + +/// Check for existing file (returns error message if check fails) +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; + +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +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"); + +namespace detail { +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail +/// @} + + + + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +// The implementation of the extra validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +} // namespace detail + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string{}; + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +// Static is not needed here, because global const implies static. + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + // NOLINTNEXTLINE + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +} // namespace detail + /// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::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 : std::uint8_t { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +#if CLI11_HAS_FILESYSTEM +namespace detail { +enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 }; +class PermissionValidator : public Validator { + public: + explicit PermissionValidator(Permission permission); +}; +} // namespace detail + +/// Check that the file exist and available for read +const detail::PermissionValidator ReadPermissions(detail::Permission::read); + +/// Check that the file exist and available for write +const detail::PermissionValidator WritePermissions(detail::Permission::write); + +/// Check that the file exist and available for write +const detail::PermissionValidator ExecPermissions(detail::Permission::exec); +#endif + +#endif + + + +namespace detail { + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto cdot = std::count(ip_addr.begin(), ip_addr.end(), '.'); + if(cdot != 3u) { + return std::string("Invalid IPV4 address: must have 3 separators"); + } + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +} // namespace detail + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail {} // namespace detail +/// @} + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE PermissionValidator::PermissionValidator(Permission permission) { + std::filesystem::perms permission_code = std::filesystem::perms::none; + std::string permission_name; + switch(permission) { + case Permission::read: + permission_code = std::filesystem::perms::owner_read | std::filesystem::perms::group_read | + std::filesystem::perms::others_read; + permission_name = "read"; + break; + case Permission::write: + permission_code = std::filesystem::perms::owner_write | std::filesystem::perms::group_write | + std::filesystem::perms::others_write; + permission_name = "write"; + break; + case Permission::exec: + permission_code = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | + std::filesystem::perms::others_exec; + permission_name = "exec"; + break; + case Permission::none: + default: + permission_code = std::filesystem::perms::none; + break; + } + func_ = [permission_code](std::string &path) { + std::error_code ec; + auto p = std::filesystem::path(path); + if(!std::filesystem::exists(p, ec)) { + return std::string("Path does not exist: ") + path; + } + if(ec) { + return std::string("Error checking path: ") + ec.message(); // LCOV_EXCL_LINE + } + if(permission_code == std::filesystem::perms::none) { + return std::string{}; + } + auto perms = std::filesystem::status(p, ec).permissions(); + if(ec) { + return std::string("Error checking path status: ") + ec.message(); // LCOV_EXCL_LINE + } + if((perms & permission_code) == std::filesystem::perms::none) { + return std::string("Path does not have required permissions: ") + path; + } + return std::string{}; + }; + description("Path with " + permission_name + " permission"); +} +#endif + +} // namespace detail +#endif + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode : std::uint8_t { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the left column (options/flags/subcommands) + std::size_t column_width_{30}; + + /// The alignment ratio for long options within the left column + float long_option_alignment_ratio_{1 / 3.f}; + + /// The width of the right column (description of options/flags/subcommands) + std::size_t right_column_width_{65}; + + /// The width of the description paragraph at the top of help + std::size_t description_paragraph_width_{80}; + + /// The width of the footer paragraph + std::size_t footer_paragraph_width_{80}; + + /// options controlling formatting for footer and descriptions + bool enable_description_formatting_{true}; + bool enable_footer_formatting_{true}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" or other labels + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the left column width (options/flags/subcommands) + void column_width(std::size_t val) { column_width_ = val; } + + /// Set the alignment ratio for long options within the left column + /// The ratio is in [0;1] range (e.g. 0.2 = 20% of column width, 6.f/column_width = 6th character) + void long_option_alignment_ratio(float ratio) { long_option_alignment_ratio_ = ratio; } + + /// Set the right column width (description of options/flags/subcommands) + void right_column_width(std::size_t val) { right_column_width_ = val; } + + /// Set the description paragraph width at the top of help + void description_paragraph_width(std::size_t val) { description_paragraph_width_ = val; } + + /// Set the footer paragraph width + void footer_paragraph_width(std::size_t val) { footer_paragraph_width_ = val; } + /// enable formatting for description paragraph + void enable_description_formatting(bool value = true) { enable_description_formatting_ = value; } + /// disable formatting for footer paragraph + void enable_footer_formatting(bool value = true) { enable_footer_formatting_ = value; } + ///@} + /// @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_; } + + /// Get the current status of description paragraph formatting + CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const { return enable_description_formatting_; } + + /// Get the current status of whether footer paragraph formatting is enabled + CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const { return enable_footer_formatting_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + CLI11_NODISCARD virtual std::string + make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub, AppFormatMode mode) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *app, std::string, AppFormatMode mode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *, bool) const; + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; +class ConfigBase; + +using Option_p = std::unique_ptr