diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d44225ce41d6f3e2bcdd32886b4ab5a23e601006..68eaca4ed3a2a944e32510b744c9294bf0e4d417 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -160,6 +160,8 @@ gkfwd:integration: parallel: matrix: - SUBTEST: [ forwarding ] + rules: + - when: never script: ## run tests @@ -297,8 +299,9 @@ coverage:baseline: coverage: stage: report image: gekkofs/testing:0.9.2 - needs: [ 'coverage:baseline', 'gkfs:integration', 'gkfwd:integration', - 'gkfs:unit' ] +# needs: [ 'coverage:baseline', 'gkfs:integration', 'gkfwd:integration', +# 'gkfs:unit' ] + needs: [ 'coverage:baseline', 'gkfs:integration', 'gkfs:unit' ] script: - cd ${CI_PROJECT_DIR} - cmake diff --git a/.gitmodules b/.gitmodules index 71b5722a3a194ed8a774f66c8de440457ec22641..18f937ae7b811d4f78fc6fce7f89600199f28abd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "external/hermes"] path = external/hermes url = https://github.com/bsc-ssrg/hermes.git -[submodule "external/fmt"] - path = external/fmt - url = https://github.com/fmtlib/fmt [submodule "tests/scripts/bats"] path = tests/scripts/bats url = https://github.com/bats-core/bats-core.git @@ -19,3 +16,9 @@ [submodule "external/spdlog"] path = external/spdlog url = https://github.com/gabime/spdlog +[submodule "external/fmt"] + path = external/fmt + url = https://github.com/fmtlib/fmt.git +[submodule "external/CLI11"] + path = external/CLI11 + url = https://github.com/CLIUtils/CLI11.git diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f3aeadf8a273db4e67a9b5bec0668f0ca880ad..d1ca56e57e0c18093d5896ca0366cec55c4f8eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,18 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - FLOCK and fcntl functions for locks, are not supported, but they are available. - Added support for [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) to simplify build configurations ([!163](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/163#note_8179)). +- Several improvements to CMake scripts ([!143](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/143))): + - Dependency management is now handled more consistently: system + dependencies are found using `find_package()`, whereas source-only + dependencies are found using `include_from_source()`. This new function + integrates a dependency provided its source code is available at + `GKFS_DEPENDENCIES_PATH`. If it's not, it will try to + automatically download it from its git repository using CMake's + `FetchContent()`. + - More consistent use of targets (we are closer to 100% modern CMake). + - Adds the `gkfs_feature_summary()` to allow printing a summary of all + GekkoFS configuration options and their values. This should help users + when building to precisely see how a GekkoFS instance has been configured. ### Changed diff --git a/CMake/FindAGIOS.cmake b/CMake/FindAGIOS.cmake index 4ef139eb4781808fa792f445c3ab48a728995609..e8c8cb10087eaf35dd0aa36d629f1a909e664fb5 100644 --- a/CMake/FindAGIOS.cmake +++ b/CMake/FindAGIOS.cmake @@ -26,21 +26,36 @@ # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ -find_path(AGIOS_INCLUDE_DIR - NAMES agios.h +find_path( + AGIOS_INCLUDE_DIR + NAMES agios.h + PATH_SUFFIXES include ) -find_library(AGIOS_LIBRARY - NAMES agios -) +find_library(AGIOS_LIBRARY NAMES agios) set(AGIOS_INCLUDE_DIRS ${AGIOS_INCLUDE_DIR}) set(AGIOS_LIBRARIES ${AGIOS_LIBRARY}) +mark_as_advanced(AGIOS_LIBRARY AGIOS_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(AGIOS DEFAULT_MSG AGIOS_LIBRARIES AGIOS_INCLUDE_DIRS) +find_package_handle_standard_args( + AGIOS + FOUND_VAR AGIOS_FOUND + REQUIRED_VARS AGIOS_LIBRARY AGIOS_INCLUDE_DIR +) + +if(AGIOS_FOUND) + set(AGIOS_INCLUDE_DIRS ${AGIOS_INCLUDE_DIR}) + set(AGIOS_LIBRARIES ${AGIOS_LIBRARY}) + if(NOT TARGET AGIOS::AGIOS) + add_library(AGIOS::AGIOS UNKNOWN IMPORTED) + set_target_properties( + AGIOS::AGIOS + PROPERTIES IMPORTED_LOCATION "${AGIOS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${AGIOS_INCLUDE_DIR}" + ) + endif() +endif( ) -mark_as_advanced( - AGIOS_LIBRARY - AGIOS_INCLUDE_DIR -) \ No newline at end of file diff --git a/CMake/FindLZ4.cmake b/CMake/FindAIO.cmake similarity index 78% rename from CMake/FindLZ4.cmake rename to CMake/FindAIO.cmake index a82ff81e698254274eb7531f996813cee4700bec..a1546d86f0e13e7b7346e1b11155067e1981af7a 100644 --- a/CMake/FindLZ4.cmake +++ b/CMake/FindAIO.cmake @@ -1,3 +1,4 @@ + ################################################################################ # Copyright 2018-2022, Barcelona Supercomputing Center (BSC), Spain # # Copyright 2015-2022, Johannes Gutenberg Universitaet Mainz, Germany # @@ -25,29 +26,33 @@ # # # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ +find_path( + AIO_INCLUDE_DIR + NAMES aio.h + PATH_SUFFIXES include +) -# - Find Lz4 -# Find the lz4 compression library and includes -# -# LZ4_FOUND - True if lz4 found. -# LZ4_LIBRARIES - List of libraries when using lz4. -# LZ4_INCLUDE_DIR - where to find lz4.h, etc. +find_library(AIO_LIBRARY NAMES rt) -find_path(LZ4_INCLUDE_DIR - NAMES lz4.h -) +mark_as_advanced(AIO_INCLUDE_DIR AIO_LIBRARY) -find_library(LZ4_LIBRARY - NAMES lz4 +find_package_handle_standard_args( + AIO + FOUND_VAR AIO_FOUND + REQUIRED_VARS AIO_INCLUDE_DIR AIO_LIBRARY ) -set(LZ4_LIBRARIES ${LZ4_LIBRARY} ) -set(LZ4_INCLUDE_DIRS ${LZ4_INCLUDE_DIR} ) +if(AIO_FOUND AND NOT TARGET AIO::AIO) + add_library(AIO::AIO UNKNOWN IMPORTED) + if(AIO_INCLUDE_DIR) + set_target_properties( + AIO::AIO PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${AIO_INCLUDE_DIR}" + ) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LZ4 DEFAULT_MSG LZ4_LIBRARY LZ4_INCLUDE_DIR) + set_target_properties( + AIO::AIO PROPERTIES IMPORTED_LOCATION "${AIO_LIBRARY}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + ) +endif() -mark_as_advanced( - LZ4_LIBRARY - LZ4_INCLUDE_DIR -) diff --git a/CMake/FindArgobots.cmake b/CMake/FindArgobots.cmake new file mode 100644 index 0000000000000000000000000000000000000000..3a7db506e021775d461429fbebf1a3a192b751d0 --- /dev/null +++ b/CMake/FindArgobots.cmake @@ -0,0 +1,213 @@ +################################################################################ +# Copyright 2018-2022, Barcelona Supercomputing Center (BSC), Spain # +# Copyright 2015-2022, Johannes Gutenberg Universitaet Mainz, Germany # +# # +# This software was partially supported by the # +# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). # +# # +# This software was partially supported by the # +# ADA-FS project under the SPPEXA project funded by the DFG. # +# # +# This file is part of GekkoFS. # +# # +# GekkoFS is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# GekkoFS is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with GekkoFS. If not, see . # +# # +# SPDX-License-Identifier: GPL-3.0-or-later # +################################################################################ + +#[=======================================================================[.rst: +FindArgobots +--------- + +Find Argobots include dirs and libraries. + +Use this module by invoking find_package with the form:: + + find_package(Argobots + [version] [EXACT] # Minimum or EXACT version e.g. 0.6.2 + [REQUIRED] # Fail with error if Argobots is not found + ) + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``Argobots::Argobots`` + The Argobots library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``Argobots_FOUND`` + True if the system has the Argobots library. +``Argobots_VERSION`` + The version of the Argobots library which was found. +``Argobots_INCLUDE_DIRS`` + Include directories needed to use Argobots. +``Argobots_LIBRARIES`` + Libraries needed to link to Argobots. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``ARGOBOTS_INCLUDE_DIR`` + The directory containing ``abt.h``. +``ARGOBOTS_LIBRARY`` + The path to the Argobots library. + +#]=======================================================================] + +function(_get_pkgconfig_paths target_var) + set(_lib_dirs) + if(NOT DEFINED CMAKE_SYSTEM_NAME + OR (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU)$" + AND NOT CMAKE_CROSSCOMPILING) + ) + if(EXISTS "/etc/debian_version") # is this a debian system ? + if(CMAKE_LIBRARY_ARCHITECTURE) + list(APPEND _lib_dirs "lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig") + endif() + else() + # not debian, check the FIND_LIBRARY_USE_LIB32_PATHS and FIND_LIBRARY_USE_LIB64_PATHS properties + get_property(uselib32 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB32_PATHS) + if(uselib32 AND CMAKE_SIZEOF_VOID_P EQUAL 4) + list(APPEND _lib_dirs "lib32/pkgconfig") + endif() + get_property(uselib64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS) + if(uselib64 AND CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND _lib_dirs "lib64/pkgconfig") + endif() + get_property(uselibx32 GLOBAL PROPERTY FIND_LIBRARY_USE_LIBX32_PATHS) + if(uselibx32 AND CMAKE_INTERNAL_PLATFORM_ABI STREQUAL "ELF X32") + list(APPEND _lib_dirs "libx32/pkgconfig") + endif() + endif() + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND NOT CMAKE_CROSSCOMPILING) + list(APPEND _lib_dirs "libdata/pkgconfig") + endif() + list(APPEND _lib_dirs "lib/pkgconfig") + list(APPEND _lib_dirs "share/pkgconfig") + + set(_extra_paths) + list(APPEND _extra_paths ${CMAKE_PREFIX_PATH}) + list(APPEND _extra_paths ${CMAKE_FRAMEWORK_PATH}) + list(APPEND _extra_paths ${CMAKE_APPBUNDLE_PATH}) + + # Check if directories exist and eventually append them to the + # pkgconfig path list + foreach(_prefix_dir ${_extra_paths}) + foreach(_lib_dir ${_lib_dirs}) + if(EXISTS "${_prefix_dir}/${_lib_dir}") + list(APPEND _pkgconfig_paths "${_prefix_dir}/${_lib_dir}") + list(REMOVE_DUPLICATES _pkgconfig_paths) + endif() + endforeach() + endforeach() + + set("${target_var}" + ${_pkgconfig_paths} + PARENT_SCOPE + ) +endfunction() + +# prevent repeating work if the main CMakeLists.txt already called +# find_package(PkgConfig) +if(NOT PKG_CONFIG_FOUND) + find_package(PkgConfig) +endif() + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_ARGOBOTS QUIET argobots) + + find_path( + ARGOBOTS_INCLUDE_DIR + NAMES abt.h + PATHS ${PC_ARGOBOTS_INCLUDE_DIRS} + PATH_SUFFIXES include + ) + + find_library( + ARGOBOTS_LIBRARY + NAMES abt + PATHS ${PC_ARGOBOTS_LIBRARY_DIRS} + ) + + set(Argobots_VERSION ${PC_ARGOBOTS_VERSION}) +else() + find_path( + ARGOBOTS_INCLUDE_DIR + NAMES abt.h + PATH_SUFFIXES include + ) + + find_library(ARGOBOTS_LIBRARY NAMES abt) + + # even if pkg-config is not available, but Argobots still installs a .pc file + # that we can use to retrieve library information from. Try to find it at all + # possible pkgconfig subfolders (depending on the system). + _get_pkgconfig_paths(_pkgconfig_paths) + + find_file(_argobots_pc_file argobots.pc PATHS "${_pkgconfig_paths}") + + if(NOT _argobots_pc_file) + message( + FATAL_ERROR + "ERROR: Could not find 'argobots.pc' file. Unable to determine library version" + ) + endif() + + file(STRINGS "${_argobots_pc_file}" _argobots_pc_file_contents + REGEX "Version: " + ) + + if("${_argobots_pc_file_contents}" MATCHES + "Version: ([0-9]+\\.[0-9]+\\.[0-9])" + ) + set(Argobots_VERSION ${CMAKE_MATCH_1}) + else() + message(FATAL_ERROR "ERROR: Failed to determine library version") + endif() + + unset(_pkg_config_paths) + unset(_argobots_pc_file_contents) +endif() + +mark_as_advanced(ARGOBOTS_INCLUDE_DIR ARGOBOTS_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Argobots + FOUND_VAR Argobots_FOUND + REQUIRED_VARS ARGOBOTS_LIBRARY ARGOBOTS_INCLUDE_DIR + VERSION_VAR Argobots_VERSION +) + +if(Argobots_FOUND) + set(Argobots_INCLUDE_DIRS ${ARGOBOTS_INCLUDE_DIR}) + set(Argobots_LIBRARIES ${ARGOBOTS_LIBRARY}) + if(NOT TARGET Argobots::Argobots) + add_library(Argobots::Argobots UNKNOWN IMPORTED) + set_target_properties( + Argobots::Argobots + PROPERTIES IMPORTED_LOCATION "${ARGOBOTS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ARGOBOTS_INCLUDE_DIR}" + ) + endif() +endif() diff --git a/CMake/FindMargo.cmake b/CMake/FindMargo.cmake index ec3f3193c41de41c9656906dcd0d2e2d8b07ff8a..b79b7affa372d28efbd0c7d1a6b93dab6ef309b9 100644 --- a/CMake/FindMargo.cmake +++ b/CMake/FindMargo.cmake @@ -26,41 +26,184 @@ # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ -# Try to find Margo headers and library. -# -# Usage of this module as follows: -# -# find_package(Margo) -# -# Variables used by this module, they can change the default behaviour and need -# to be set before calling find_package: -# -# MARGO_ROOT_DIR Set this variable to the root installation of -# Margo if the module has problems finding the -# proper installation path. -# -# Variables defined by this module: -# -# MARGO_FOUND System has Margo library/headers. -# MARGO_LIBRARIES The Margo library. -# MARGO_INCLUDE_DIRS The location of Margo headers. - - -find_path(MARGO_INCLUDE_DIR +#[=======================================================================[.rst: +FindMargo +--------- + +Find Margo include dirs and libraries. + +Use this module by invoking find_package with the form:: + + find_package(Margo + [version] [EXACT] # Minimum or EXACT version e.g. 0.6.2 + [REQUIRED] # Fail with error if Margo is not found + ) + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``Margo::Margo`` + The Margo library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``Margo_FOUND`` + True if the system has the Margo library. +``Margo_VERSION`` + The version of the Margo library which was found. +``Margo_INCLUDE_DIRS`` + Include directories needed to use Margo. +``Margo_LIBRARIES`` + Libraries needed to link to Margo. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``MARGO_INCLUDE_DIR`` + The directory containing ``margo.h``. +``MARGO_LIBRARY`` + The path to the Margo library. + +#]=======================================================================] + +function(_get_pkgconfig_paths target_var) + set(_lib_dirs) + if(NOT DEFINED CMAKE_SYSTEM_NAME + OR (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU)$" + AND NOT CMAKE_CROSSCOMPILING) + ) + if(EXISTS "/etc/debian_version") # is this a debian system ? + if(CMAKE_LIBRARY_ARCHITECTURE) + list(APPEND _lib_dirs "lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig") + endif() + else() + # not debian, check the FIND_LIBRARY_USE_LIB32_PATHS and FIND_LIBRARY_USE_LIB64_PATHS properties + get_property(uselib32 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB32_PATHS) + if(uselib32 AND CMAKE_SIZEOF_VOID_P EQUAL 4) + list(APPEND _lib_dirs "lib32/pkgconfig") + endif() + get_property(uselib64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS) + if(uselib64 AND CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND _lib_dirs "lib64/pkgconfig") + endif() + get_property(uselibx32 GLOBAL PROPERTY FIND_LIBRARY_USE_LIBX32_PATHS) + if(uselibx32 AND CMAKE_INTERNAL_PLATFORM_ABI STREQUAL "ELF X32") + list(APPEND _lib_dirs "libx32/pkgconfig") + endif() + endif() + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND NOT CMAKE_CROSSCOMPILING) + list(APPEND _lib_dirs "libdata/pkgconfig") + endif() + list(APPEND _lib_dirs "lib/pkgconfig") + list(APPEND _lib_dirs "share/pkgconfig") + + set(_extra_paths) + list(APPEND _extra_paths ${CMAKE_PREFIX_PATH}) + list(APPEND _extra_paths ${CMAKE_FRAMEWORK_PATH}) + list(APPEND _extra_paths ${CMAKE_APPBUNDLE_PATH}) + + # Check if directories exist and eventually append them to the + # pkgconfig path list + foreach(_prefix_dir ${_extra_paths}) + foreach(_lib_dir ${_lib_dirs}) + if(EXISTS "${_prefix_dir}/${_lib_dir}") + list(APPEND _pkgconfig_paths "${_prefix_dir}/${_lib_dir}") + list(REMOVE_DUPLICATES _pkgconfig_paths) + endif() + endforeach() + endforeach() + + set("${target_var}" + ${_pkgconfig_paths} + PARENT_SCOPE + ) +endfunction() + +# prevent repeating work if the main CMakeLists.txt already called +# find_package(PkgConfig) +if(NOT PKG_CONFIG_FOUND) + find_package(PkgConfig) +endif() + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_MARGO QUIET margo) + + find_path( + MARGO_INCLUDE_DIR NAMES margo.h -) + PATHS ${PC_MARGO_INCLUDE_DIRS} + PATH_SUFFIXES include + ) -find_library(MARGO_LIBRARY + find_library( + MARGO_LIBRARY NAMES margo -) + PATHS ${PC_MARGO_LIBRARY_DIRS} + ) + + set(Margo_VERSION ${PC_MARGO_VERSION}) +else() + find_path( + MARGO_INCLUDE_DIR + NAMES margo.h + PATH_SUFFIXES include + ) -set(MARGO_INCLUDE_DIRS ${MARGO_INCLUDE_DIR}) -set(MARGO_LIBRARIES ${MARGO_LIBRARY}) + find_library(MARGO_LIBRARY NAMES margo) + + # even if pkg-config is not available, but Margo still installs a .pc file + # that we can use to retrieve library information from. Try to find it at all + # possible pkgconfig subfolders (depending on the system). + _get_pkgconfig_paths(_pkgconfig_paths) + + find_file(_margo_pc_file margo.pc PATHS "${_pkgconfig_paths}") + + if(NOT _margo_pc_file) + message( + FATAL_ERROR + "ERROR: Could not find 'margo.pc' file. Unable to determine library version" + ) + endif() + + file(STRINGS "${_margo_pc_file}" _margo_pc_file_contents REGEX "Version: ") + + if("${_margo_pc_file_contents}" MATCHES "Version: ([0-9]+\\.[0-9]+\\.[0-9])") + set(Margo_VERSION ${CMAKE_MATCH_1}) + else() + message(FATAL_ERROR "ERROR: Failed to determine library version") + endif() + + unset(_pkg_config_paths) + unset(_margo_pc_file_contents) +endif() + +mark_as_advanced(MARGO_INCLUDE_DIR MARGO_LIBRARY) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Margo DEFAULT_MSG MARGO_LIBRARY MARGO_INCLUDE_DIR) +find_package_handle_standard_args( + Margo + FOUND_VAR Margo_FOUND + REQUIRED_VARS MARGO_LIBRARY MARGO_INCLUDE_DIR + VERSION_VAR Margo_VERSION +) -mark_as_advanced( - MARGO_LIBRARY - MARGO_INCLUDE_DIR -) \ No newline at end of file +if(Margo_FOUND) + set(Margo_INCLUDE_DIRS ${MARGO_INCLUDE_DIR}) + set(Margo_LIBRARIES ${MARGO_LIBRARY}) + if(NOT TARGET Margo::Margo) + add_library(Margo::Margo UNKNOWN IMPORTED) + set_target_properties( + Margo::Margo + PROPERTIES IMPORTED_LOCATION "${MARGO_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MARGO_INCLUDE_DIR}" + ) + endif() +endif() diff --git a/CMake/FindMercury.cmake b/CMake/FindMercury.cmake new file mode 100644 index 0000000000000000000000000000000000000000..fd83231c1095dda367a3220de15324c1b7bcff54 --- /dev/null +++ b/CMake/FindMercury.cmake @@ -0,0 +1,280 @@ +################################################################################ +# Copyright 2018-2022, Barcelona Supercomputing Center (BSC), Spain # +# Copyright 2015-2022, Johannes Gutenberg Universitaet Mainz, Germany # +# # +# This software was partially supported by the # +# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). # +# # +# This software was partially supported by the # +# ADA-FS project under the SPPEXA project funded by the DFG. # +# # +# This file is part of GekkoFS. # +# # +# GekkoFS is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# GekkoFS is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with GekkoFS. If not, see . # +# # +# SPDX-License-Identifier: GPL-3.0-or-later # +################################################################################ + +#[=======================================================================[.rst: +FindMercury +--------- + +Find Mercury include dirs and libraries. + +Use this module by invoking find_package with the form:: + + find_package(Mercury + [version] [EXACT] # Minimum or EXACT version e.g. 0.6.2 + [REQUIRED] # Fail with error if Mercury is not found + ) + +Some variables one may set to control this module are:: + + Mercury_DEBUG - Set to ON to enable debug output from FindMercury. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``Mercury::Mercury`` + The Mercury library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``Mercury_FOUND`` + True if the system has the Mercury library. +``Mercury_VERSION`` + The version of the Mercury library which was found. +``Mercury_INCLUDE_DIRS`` + Include directories needed to use Mercury. +``Mercury_LIBRARIES`` + Libraries needed to link to Mercury. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``MERCURY_INCLUDE_DIR`` + The directory containing ``mercury.h``. +``MERCURY_LIBRARY`` + The path to the Mercury library. + +#]=======================================================================] + +# +# Print debug text if Mercury_DEBUG is set. +# Call example: +# _mercury_DEBUG_PRINT("debug message") +# +function(_mercury_DEBUG_PRINT text) + if(Mercury_DEBUG) + message( + STATUS + "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_FUNCTION_LIST_LINE} ] ${text}" + ) + endif() +endfunction() + +macro(_mercury_find_component _component_name) + + _mercury_debug_print("Searching for Mercury component: ${_component}") + + string(TOUPPER ${_component} _upper_component) + + # find component header + find_path( + ${_upper_component}_INCLUDE_DIR ${_component} + NAMES ${_component}.h + PATH_SUFFIXES include + PATHS ${PC_MERCURY_INCLUDE_DIRS} + ) + + # find component library (release version) + find_library( + ${_upper_component}_LIBRARY_RELEASE + NAMES ${_component} + PATHS ${PC_MERCURY_LIBRARY_DIRS} + ) + + # find component library (debug version) + find_library( + ${_upper_component}_LIBRARY_DEBUG + NAMES ${_component}_debug + PATHS ${PC_MERCURY_LIBRARY_DIRS} + ) + + # initialize ${_upper_component}_LIBRARY (e.g. NA_LIBRARY) + # with the appropriate library for this build configuration + select_library_configurations(${_upper_component}) + + _mercury_debug_print( + "${_upper_component}_INCLUDE_DIR: ${${_upper_component}_INCLUDE_DIR}" + ) + _mercury_debug_print( + "${_upper_component}_LIBRARY: ${${_upper_component}_LIBRARY}" + ) + + # define an imported target for the component + if (NOT TARGET Mercury::${_component}) + add_library(Mercury::${_component} UNKNOWN IMPORTED) + endif () + + if(${_upper_component}_LIBRARY_RELEASE) + set_property(TARGET Mercury::${_component} APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(Mercury::${_component} PROPERTIES + IMPORTED_LOCATION_RELEASE "${${_upper_component}_LIBRARY_RELEASE}" + ) + endif() + + if(${_upper_component}_LIBRARY_DEBUG) + set_property(TARGET Mercury::${_component} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(Mercury::${_component} PROPERTIES + IMPORTED_LOCATION_DEBUG "${${_upper_component}_LIBRARY_DEBUG}" + IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" + ) + endif() + + set_target_properties( + Mercury::${_component} + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${MERCURY_INCLUDE_DIR}" + ) + + get_target_property(_configs Mercury::${_component} IMPORTED_CONFIGURATIONS) + foreach(_config ${_configs}) + _mercury_DEBUG_PRINT("---> ${_config}") + + endforeach() + + set_property(TARGET Mercury::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES ) + + + mark_as_advanced(${_upper_component}_INCLUDE_DIR ${_upper_component}_LIBRARY) + +endmacro() + +############################################################################### +# Find Mercury headers and library +############################################################################### + +include(SelectLibraryConfigurations) + +set(_mercury_components na mchecksum mercury_util mercury_hl) + +# prevent repeating work if the main CMakeLists.txt already called +# find_package(PkgConfig) +if(NOT PKG_CONFIG_FOUND) + find_package(PkgConfig REQUIRED) +endif() + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_MERCURY QUIET mercury) + + # find mercury header + find_path( + MERCURY_INCLUDE_DIR + NAMES mercury.h + PATHS ${PC_MERCURY_INCLUDE_DIRS} + PATH_SUFFIXES include + ) + # find mercury library (release version) + find_library( + MERCURY_LIBRARY_RELEASE + NAMES mercury + PATHS ${PC_MERCURY_LIBRARY_DIRS} + ) + + # find mercury library (debug version) + find_library( + MERCURY_LIBRARY_DEBUG + NAMES mercury_debug + PATHS ${PC_MERCURY_LIBRARY_DIRS} + ) + + # initialize MERCURY_LIBRARY with the appropriate library for this build + # configuration + select_library_configurations(MERCURY) + + # Mercury_VERSION needs to be set to the appropriate value for the call + # to find_package_handle_standard_args() below to work as expected + set(Mercury_VERSION ${PC_MERCURY_VERSION}) +endif() + +mark_as_advanced(MERCURY_INCLUDE_DIR MERCURY_LIBRARY) + + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Mercury + FOUND_VAR Mercury_FOUND + REQUIRED_VARS MERCURY_LIBRARY MERCURY_INCLUDE_DIR + VERSION_VAR Mercury_VERSION +) + +if(Mercury_FOUND) + set(Mercury_INCLUDE_DIRS ${MERCURY_INCLUDE_DIR}) + set(Mercury_LIBRARIES ${MERCURY_LIBRARY}) + + if(NOT TARGET Mercury::Mercury) + add_library(Mercury::Mercury UNKNOWN IMPORTED) + endif() + + if(MERCURY_LIBRARY_RELEASE) + set_property( + TARGET Mercury::Mercury + APPEND + PROPERTY IMPORTED_CONFIGURATIONS RELEASE + ) + set_target_properties( + Mercury::Mercury PROPERTIES IMPORTED_LOCATION_RELEASE + "${MERCURY_LIBRARY_RELEASE}" + ) + endif() + + if(MERCURY_LIBRARY_DEBUG) + set_property( + TARGET Mercury::Mercury + APPEND + PROPERTY IMPORTED_CONFIGURATIONS DEBUG + ) + set_target_properties( + Mercury::Mercury PROPERTIES IMPORTED_LOCATION_DEBUG + "${MERCURY_LIBRARY_DEBUG}" + ) + endif() + + set_target_properties( + Mercury::Mercury + PROPERTIES INTERFACE_COMPILE_OPTIONS "${PC_MERCURY_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${MERCURY_INCLUDE_DIR}" + + ) + + ############################################################################### + # Find headers and libraries for additional Mercury components + ############################################################################### + foreach(_component ${_mercury_components}) + _mercury_find_component(${_component}) + + set_target_properties(Mercury::Mercury PROPERTIES + INTERFACE_LINK_LIBRARIES Mercury::${_component}) + endforeach() +endif() diff --git a/CMake/FindParallax.cmake b/CMake/FindParallax.cmake index 562ad6b9ce2f9c8e5f9a8469094cb32dc312c7a3..e2b85c646dc1564b9405c0344c5536e42b7afb2e 100644 --- a/CMake/FindParallax.cmake +++ b/CMake/FindParallax.cmake @@ -25,27 +25,59 @@ # # # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ -find_path(PARALLAX_INCLUDE_DIR - NAMES parallax.h - ) -find_library(PARALLAX_LIBRARY - NAMES parallax - ) +list(APPEND _parallax_components "log") -find_library(PARALLAX_LOG_LIBRARY - NAMES log - ) +find_path( + PARALLAX_INCLUDE_DIR + NAMES parallax.h + PATH_SUFFIXES include +) -set(PARALLAX_INCLUDE_DIRS ${PARALLAX_INCLUDE_DIR}) -set(PARALLAX_LIBRARIES ${PARALLAX_LIBRARY}) -set(PARALLAX_LIBRARIES ${PARALLAX_LOG_LIBRARY}) +find_library(PARALLAX_LIBRARY NAMES parallax) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Parallax DEFAULT_MSG PARALLAX_LIBRARY PARALLAX_LOG_LIBRARY PARALLAX_INCLUDE_DIR) +find_library(PARALLAX_LOG_LIBRARY NAMES log) -mark_as_advanced( - PARALLAX_LIBRARY - PARALLAX_INCLUDE_DIR - PARALLAX_LOG_LIBRARY +mark_as_advanced(PARALLAX_LIBRARY PARALLAX_INCLUDE_DIR PARALLAX_LOG_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Parallax + FOUND_VAR Parallax_FOUND + REQUIRED_VARS PARALLAX_LIBRARY PARALLAX_LOG_LIBRARY PARALLAX_INCLUDE_DIR ) + +if(Parallax_FOUND) + set(Parallax_INCLUDE_DIRS ${PARALLAX_INCLUDE_DIR}) + set(Parallax_LIBRARIES ${PARALLAX_LIBRARY} ${PARALLAX_LOG_LIBRARY}) + + if(NOT TARGET Parallax::parallax) + add_library(Parallax::parallax UNKNOWN IMPORTED) + endif() + + set_target_properties( + Parallax::parallax + PROPERTIES IMPORTED_LOCATION "${PARALLAX_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PARALLAX_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) + + foreach(_component ${_parallax_components}) + if(NOT TARGET Parallax::${_component}) + add_library(Parallax::${_component} UNKNOWN IMPORTED) + + if(Parallax_INCLUDE_DIRS) + set_target_properties( + Parallax::${_component} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${PARALLAX_INCLUDE_DIR}" + ) + + set_target_properties( + Parallax::${_component} + PROPERTIES IMPORTED_LOCATION "${PARALLAX_LOG_LIBRARY}" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) + endif() + endif() + endforeach() +endif() diff --git a/CMake/gkfs-options.cmake b/CMake/gkfs-options.cmake new file mode 100644 index 0000000000000000000000000000000000000000..598de903ca9a82d9dd4496a2bd96a704792fe2ad --- /dev/null +++ b/CMake/gkfs-options.cmake @@ -0,0 +1,373 @@ +################################################################################ +# Copyright 2018-2022, Barcelona Supercomputing Center (BSC), Spain # +# Copyright 2015-2022, Johannes Gutenberg Universitaet Mainz, Germany # +# # +# This software was partially supported by the # +# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). # +# # +# This software was partially supported by the # +# ADA-FS project under the SPPEXA project funded by the DFG. # +# # +# This file is part of GekkoFS. # +# # +# GekkoFS is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# GekkoFS is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with GekkoFS. If not, see . # +# # +# SPDX-License-Identifier: GPL-3.0-or-later # +################################################################################ + +function(_get_feature_summary _output _property) + + set(_currentFeatureText "") + get_property(_features GLOBAL PROPERTY ${_property}) + + if(_features) + list(REMOVE_DUPLICATES _features) + endif() + + foreach(_f ${_features}) + string(APPEND _currentFeatureText "\n-- * ${_f}") + get_property(_info GLOBAL PROPERTY _CMAKE_${_f}_DESCRIPTION) + if(_info) + string(APPEND _currentFeatureText ": ${_info}") + else() + get_property(_info GLOBAL PROPERTY _CMAKE_${_f}_HELP_TEXT) + string(APPEND _currentFeatureText ": ${_info}") + endif() + get_property(_info GLOBAL PROPERTY _CMAKE_${_f}_DEPENDS) + if(_info) + string(APPEND _currentFeatureText " [${${_info}}]") + endif() + get_property(_info GLOBAL PROPERTY _CMAKE_${_f}_EXTRA_INFO) + if(_info) + string(APPEND _currentFeatureText "\n-- ${_info}") + endif() + + endforeach() + + set(${_output} + "${_currentFeatureText}" + PARENT_SCOPE + ) + +endfunction() + +function(_add_feature_info _name _depends _help) + set(_enabled 1) + foreach(_d ${_depends}) + string(REGEX REPLACE " +" ";" _d "${_d}") + if(${_d}) + + else() + set(_enabled 0) + break() + endif() + endforeach() + if(${_enabled}) + set_property(GLOBAL APPEND PROPERTY GKFS_ENABLED_FEATURES "${_name}") + else() + set_property(GLOBAL APPEND PROPERTY GKFS_DISABLED_FEATURES "${_name}") + endif() + + set_property(GLOBAL PROPERTY _CMAKE_${_name}_DEPENDS ${_depends}) + set_property(GLOBAL PROPERTY _CMAKE_${_name}_HELP_TEXT "${_help}") + + if(ARGC GREATER_EQUAL 4) + set_property(GLOBAL PROPERTY _CMAKE_${_name}_DESCRIPTION "${ARGV3}") + if(ARGC EQUAL 5) + set_property(GLOBAL PROPERTY _CMAKE_${_name}_EXTRA_INFO "${ARGV4}") + endif() + endif() +endfunction() + +function(gkfs_feature_summary) + + set(OPTIONS) + set(SINGLE_VALUE DESCRIPTION) + set(MULTI_VALUE) + + cmake_parse_arguments( + ARGS "${OPTIONS}" "${SINGLE_VALUE}" "${MULTI_VALUE}" ${ARGN} + ) + + if(ARGS_UNPARSED_ARGUMENTS) + message( + FATAL_ERROR + "Unknown keywords given to gkfs_feature_summary(): \"${ARGS_UNPARSED_ARGUMENTS}\"" + ) + endif() + + if(NOT ARGS_DESCRIPTION) + message( + FATAL_ERROR + "Missing mandatory keyword DESCRIPTION for gkfs_feature_summary()" + ) + endif() + + list(APPEND _what GKFS_ENABLED_FEATURES GKFS_DISABLED_FEATURES) + + set(_text "\n-- ") + string(APPEND _text "\n-- ====================================") + string(APPEND _text "\n-- ${ARGS_DESCRIPTION}") + string(APPEND _text "\n-- ====================================") + foreach(_w ${_what}) + set(_tmp) + _get_feature_summary(_tmp ${_w}) + + if(_tmp) + if(_text) + string(APPEND _text "\n-- ${_tmp}") + endif() + endif() + endforeach() + + string(APPEND _text "\n-- ") + + message(STATUS "${_text}") +endfunction() + +function(gkfs_define_option varName) + + set(OPTIONS) + set(SINGLE_VALUE HELP_TEXT DEFAULT_VALUE FEATURE_NAME DESCRIPTION EXTRA_INFO) + set(MULTI_VALUE) + + cmake_parse_arguments( + ARGS "${OPTIONS}" "${SINGLE_VALUE}" "${MULTI_VALUE}" ${ARGN} + ) + + if(ARGS_UNPARSED_ARGUMENTS) + message( + FATAL_ERROR + "Unknown keywords given to gkfs_define_option(): \"${ARGS_UNPARSED_ARGUMENTS}\"" + ) + endif() + + if(NOT ARGS_HELP_TEXT) + message( + FATAL_ERROR "Missing mandatory keyword HELP_TEXT for gkfs_define_option()" + ) + endif() + + if(NOT ARGS_FEATURE_NAME) + set(ARGS_FEATURE_NAME ${varName}) + endif() + + _add_feature_info( + ${ARGS_FEATURE_NAME} ${varName} ${ARGS_HELP_TEXT} ${ARGS_DESCRIPTION} + ${ARGS_EXTRA_INFO} + ) + + option(${varName} ${ARGS_HELP_TEXT} ${ARGS_DEFAULT_VALUE}) + +endfunction() + +function(gkfs_define_variable varName value type docstring) + + set(OPTIONS ADVANCED) + set(SINGLE_VALUE) + set(MULTI_VALUE) + + cmake_parse_arguments( + ARGS "${OPTIONS}" "${SINGLE_VALUE}" "${MULTI_VALUE}" ${ARGN} + ) + + if(ARGS_UNPARSED_ARGUMENTS) + message( + FATAL_ERROR + "Unknown keywords given to gkfs_define_variable(): \"${ARGS_UNPARSED_ARGUMENTS}\"" + ) + endif() + + set(${varName} ${value} CACHE ${type} ${docstring}) + _add_feature_info(${varName} ${varName} ${docstring}) + + if(ARGS_ADVANCED) + mark_as_advanced(varName) + endif() + +endfunction() + + +################################################################################ +# Variables and options controlling the build process +################################################################################ + +# build documentation +gkfs_define_option( + GKFS_BUILD_DOCUMENTATION + HELP_TEXT "Build documentation" + DEFAULT_VALUE OFF + DESCRIPTION "Generate documentation based on Sphinx+Doxygen" + FEATURE_NAME "DOCS" +) + +# build tests +gkfs_define_option( + GKFS_ENABLE_UNUSED_FUNCTIONS + HELP_TEXT "Enable unused functions compilation" + DEFAULT_VALUE OFF +) + + +# build tests +gkfs_define_option( + GKFS_BUILD_TESTS + HELP_TEXT "Build ${PROJECT_NAME} self tests" + DEFAULT_VALUE OFF +) + +cmake_dependent_option(GKFS_INSTALL_TESTS "Install GekkoFS self tests" OFF "GKFS_BUILD_TESTS" OFF) + + +################################################################################ +# Variables and options controlling POSIX semantics +################################################################################ + +## check before create +# FIXME: should be prefixed with GKFS_ +gkfs_define_option( + CREATE_CHECK_PARENTS + HELP_TEXT "Enable checking parent directory for existence before creating children" + DEFAULT_VALUE ON + DESCRIPTION "Verify that a parent directory exists before creating new files or directories" +) + +## symbolic link support +# FIXME: should be prefixed with GKFS_ +gkfs_define_option( + GKFS_SYMLINK_SUPPORT + HELP_TEXT "Enable support for symlinks" + DEFAULT_VALUE ON + DESCRIPTION "Enable support for symbolic links in paths" +) + +## rename support +gkfs_define_option( + GKFS_RENAME_SUPPORT + HELP_TEXT "Enable support for rename" + DEFAULT_VALUE OFF + DESCRIPTION "Compile with support for rename ops (experimental)" +) + + +################################################################################ +# Options and variables that control how GekkoFS behaves internally +################################################################################ + +## Maximum number of internal file descriptors reserved for GekkoFS +# FIXME: should be prefixed with GKFS_ +gkfs_define_variable(MAX_INTERNAL_FDS 256 + STRING "Number of file descriptors reserved for internal use" ADVANCED +) + +## Maximum number of open file descriptors for GekkoFS +# FIXME: should be prefixed with GKFS_ +execute_process(COMMAND getconf OPEN_MAX + OUTPUT_VARIABLE _GETCONF_MAX_FDS + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) +if (NOT _GETCONF_MAX_FDS) + set(_GETCONF_MAX_FDS=512) +endif () + +gkfs_define_variable( + MAX_OPEN_FDS + ${_GETCONF_MAX_FDS} + STRING + "Maximum number of open file descriptors supported" + ADVANCED) + +## RocksDB support +gkfs_define_option( + GKFS_ENABLE_ROCKSDB + HELP_TEXT "Enable RocksDB metadata backend" + DEFAULT_VALUE ON + DESCRIPTION "Use RocksDB key-value store for the metadata backend" +) + +## Parallax support +gkfs_define_option( + GKFS_ENABLE_PARALLAX + HELP_TEXT "Enable Parallax support" + DEFAULT_VALUE OFF + DESCRIPTION "Support using the Parallax key-value store in the metadata backend" +) + +## Guided distribution +gkfs_define_variable( + GKFS_USE_GUIDED_DISTRIBUTION_PATH + "/tmp/guided.txt" + STRING + "File Path for guided distributor" +) + +gkfs_define_option( + GKFS_USE_GUIDED_DISTRIBUTION + HELP_TEXT "Use guided data distributor" + DEFAULT_VALUE OFF + DESCRIPTION "Use a guided data distribution instead of GekkoFS' wide striping" + EXTRA_INFO "Guided data distributor input file path: ${GKFS_USE_GUIDED_DISTRIBUTION_PATH}" +) + + +################################################################################ +# Logging and tracing support +################################################################################ + +## Client logging support +# FIXME: should be prefixed with GKFS_ +gkfs_define_option( + ENABLE_CLIENT_LOG HELP_TEXT "Enable logging messages in clients" + DEFAULT_VALUE ON +) + +# FIXME: should be prefixed with GKFS_ +gkfs_define_variable( + CLIENT_LOG_MESSAGE_SIZE + 1024 + STRING + "Maximum size of a log message in the client library" + ADVANCED +) + +## Prometheus tracing support +gkfs_define_option( + GKFS_ENABLE_PROMETHEUS + HELP_TEXT "Enable Prometheus Push" + DEFAULT_VALUE OFF + DESCRIPTION "Enable the collection of stats using Prometheus" +) + + +################################################################################ +# I/O forwarding +################################################################################ + +## Forwarding support +gkfs_define_option( + GKFS_ENABLE_FORWARDING + HELP_TEXT "Enable I/O forwarding mode" + DEFAULT_VALUE OFF + DESCRIPTION "Use ${PROJECT_NAME} as an I/O forwarding layer" +) + +## Scheduling in I/O forwarding mode +gkfs_define_option( + GKFS_ENABLE_AGIOS + HELP_TEXT "Enable AGIOS scheduling library" + DEFAULT_VALUE OFF + DESCRIPTION "If GKFS_ENABLE_FORWARDING is ON, use AGIOS for scheduling I/Os" +) + + diff --git a/CMake/gkfs-utils.cmake b/CMake/gkfs-utils.cmake index e4ea1379d0ba3bb14a57a7eeff05b6e8ce0968bf..cdb7047d4f03c8f15fa444dee250a07ad0c5341b 100644 --- a/CMake/gkfs-utils.cmake +++ b/CMake/gkfs-utils.cmake @@ -146,3 +146,105 @@ function(mark_variables_as_advanced) mark_as_advanced(${_var}) endforeach() endfunction() + + +#[=======================================================================[.rst: + + include_from_source(contentName ...) + +The ``include_from_source()`` function ensures that ``contentName`` is +populated and potentially added to the build by the time it returns. + +**Options:** + + ``SOURCE_DIR ``: Source directory into which downloaded contents reside. + This must point to an existing directory where the external project has + already been unpacked or cloned/checked out. If ```` doesn't exist, + the source code will be retrieved. + + ``GIT_REPOSITORY `` + URL of the git repository. Any URL understood by the ``git`` command + may be used. + + ``GIT_TAG `` + Git branch name, tag or commit hash. Note that branch names and tags should + generally be specified as remote names (i.e. origin/myBranch rather than + simply myBranch). This ensures that if the remote end has its tag moved or + branch rebased or history rewritten, the local clone will still be updated + correctly. In general, however, specifying a commit hash should be + preferred for a number of reasons: + + If the local clone already has the commit corresponding to the hash, no git + fetch needs to be performed to check for changes each time CMake is re-run. + This can result in a significant speed up if many external projects are + being used. + + Using a specific git hash ensures that the main project's own history is + fully traceable to a specific point in the external project's evolution. + If a branch or tag name is used instead, then checking out a specific + commit of the main project doesn't necessarily pin the whole build to a + specific point in the life of the external project. The lack of such + deterministic behavior makes the main project lose traceability and + repeatability. + + NOTE: If both ``SOURCE_DIR`` and ``GIT_REPOSITORY`` are specified, + ``SOURCE_DIR`` will be the preferred location to populate ``contentName`` + from. If ``SOURCE_DIR`` doesn't exist, the function will fall back to the + location defined by ``GIT_REPOSITORY``. + +#]=======================================================================] +function(include_from_source contentName) + + set(OPTIONS) + set(SINGLE_VALUE MESSAGE SOURCE_DIR GIT_REPOSITORY GIT_TAG) + set(MULTI_VALUE) + + cmake_parse_arguments(ARGS "${OPTIONS}" "${SINGLE_VALUE}" "${MULTI_VALUE}" ${ARGN}) + + if(ARGS_MESSAGE) + message(STATUS ${ARGS_MESSAGE}) + endif() + + include(FetchContent) + + if (EXISTS ${ARGS_SOURCE_DIR}) + file(GLOB_RECURSE SOURCE_FILES "${ARGS_SOURCE_DIR}/*") + if(SOURCE_FILES STREQUAL "") + message(FATAL_ERROR + "The '${ARGS_SOURCE_DIR}' source directory appears " + "to be empty. If it corresponds to a git submodule it may not have " + "been properly initialized. Running:\n" + " 'git submodule update --init --recursive'\n" + "may fix the issue. If the directory corresponds to a manually " + "downloaded dependency, please download it again.") + endif() + + message(STATUS "Found source directory for '${contentName}'. Building.") + FetchContent_Declare( + ${contentName} + SOURCE_DIR ${ARGS_SOURCE_DIR} + ) + else() + message(STATUS + "Source directory for '${contentName}' not found.\n" + "Downloading and building from remote Git repository.") + + if(NOT ARGS_GIT_REPOSITORY) + message(FATAL_ERROR "GIT_REPOSITORY for \"${contentName}\" not defined") + endif() + + if(NOT ARGS_GIT_TAG) + message(FATAL_ERROR "GIT_TAG for \"${contentName}\" not defined") + endif() + + FetchContent_Declare( + ${contentName} + GIT_REPOSITORY ${ARGS_GIT_REPOSITORY} + GIT_TAG ${ARGS_GIT_TAG} + GIT_SHALLOW ON + GIT_PROGRESS ON + ) + endif() + + FetchContent_MakeAvailable(${contentName}) +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 50c839b510ae6cf724471c395086a71e5a62311d..0e701f757d39858edb1c8da2eb476fd9b4ad4d4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ -cmake_minimum_required(VERSION 3.6) +# 3.13+ so that we have a sensible handling of relative paths +cmake_minimum_required(VERSION 3.13) project( GekkoFS @@ -51,7 +52,7 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build: Debug Release Memcheck" FORCE) ENDIF (NOT CMAKE_BUILD_TYPE) -message(STATUS "[gekkofs] Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "[${PROJECT_NAME}] Build type: ${CMAKE_BUILD_TYPE}") # Compiler flags for various cmake build types set(WARNINGS_FLAGS "-Wall -Wextra --pedantic -Wno-unused-parameter -Wno-missing-field-initializers") @@ -69,6 +70,7 @@ mark_as_advanced(CMAKE_CXX_FLAGS_MAINTAINER) include(CheckCXXSourceCompiles) include(CMakeDependentOption) include(GNUInstallDirs) +include(FeatureSummary) # Project version set(GIT_VERSION_FOUND FALSE) @@ -98,7 +100,7 @@ if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git) endif () endif () endif () -message(STATUS "[gekkofs] Project version: ${PROJECT_VERSION}") +message(STATUS "[${PROJECT_NAME}] Project version: ${PROJECT_VERSION}") set(GKFS_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(GKFS_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(GKFS_VERSION_PATCH ${PROJECT_VERSION_PATCH}) @@ -107,115 +109,187 @@ configure_file(include/version.hpp.in include/version.hpp) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH}) +# Import some convenience functions +include(gkfs-utils) + +################################################################################ +## Configuration option definitions: +## ================================= +## +## The `gkfs-options` module imports definitions for all the available GekkoFS +## features +################################################################################ +include(gkfs-options) + set(CMAKE_EXPORT_COMPILE_COMMANDS 0) -# margo dependencies -find_package(Mercury REQUIRED) -find_package(Abt REQUIRED) -find_package(Margo REQUIRED) +# ############################################################################## +# Check for and/or download dependencies: +# +# GekkoFS has two types of dependencies: +# 1. Dependencies that are installed as system-wide packages and provide +# a FindXXX.cmake file to find them. These are handled using +# `find_package()`. +# 2. Depedencies that are used directly from source code (e.g. header-only +# libraries). The source code for these can be found locally or +# remotely in a GIT repository. These are handled using our custom +# `include_from_source()` function defined in `gkfs-utils.cmake` +# ############################################################################## + +# Search for dependencies that should already be installed in the system +############################################################################### + +### some dependencies don't provide CMake modules, but rely on pkg-config +### instead, make sure that pkg-config is available +find_package(PkgConfig REQUIRED) + +### Mercury +message(STATUS "[${PROJECT_NAME}] Checking for Mercury") +find_package(Mercury 2.0.1 REQUIRED) + +### Argobots +message(STATUS "[${PROJECT_NAME}] Checking for Argobots") +find_package(Argobots 1.1 REQUIRED) + +### Margo +message(STATUS "[${PROJECT_NAME}] Checking for Margo") +find_package(Margo 0.9.6 REQUIRED) + +### syscall-intercept +message(STATUS "[${PROJECT_NAME}] Checking for syscall_intercept") find_package(Syscall_intercept REQUIRED) + +### Date (required by the libgkfs_intercept.so) +message(STATUS "[${PROJECT_NAME}] Checking for Date") +find_package(Date REQUIRED) + +### AGIOS: required for scheduling I/O requests +if (GKFS_ENABLE_AGIOS) + message(STATUS "[${PROJECT_NAME}] Checking for Agios") + find_package(AGIOS REQUIRED) +endif() + +### Metadata backends +if(GKFS_ENABLE_ROCKSDB) + message(STATUS "[${PROJECT_NAME}] Checking for RocksDB") + add_compile_definitions(GKFS_ENABLE_ROCKSDB) + find_package(RocksDB 6.26.1 REQUIRED) + message(STATUS "[${PROJECT_NAME}] RocksDB version ${RocksDB_VERSION}") + message(STATUS "[${PROJECT_NAME}] RocksDB location ${RocksDB_DIR}") +endif() + +if(GKFS_ENABLE_PARALLAX) + add_compile_definitions(GKFS_ENABLE_PARALLAX) + # The current version of Parallax has a direct dependency on libyaml and librt. + # Make sure that we find them and add them as link dependencies.. + + message(STATUS "[${PROJECT_NAME}] Checking for yaml") + find_package(yaml REQUIRED) + + message(STATUS "[${PROJECT_NAME}] Checking for libaio") + find_package(AIO REQUIRED) + + message(STATUS "[${PROJECT_NAME}] Checking for Parallax") + find_package(Parallax REQUIRED) + target_link_libraries(Parallax::parallax INTERFACE yaml AIO::AIO) +endif() + +### Prometheus-cpp: required for the collection of GekkoFS stats +### (these expose the prometheus-cpp::pull, prometheus-cpp::push, +### prometheus-cpp::core, and curl imported targets +if(GKFS_ENABLE_PROMETHEUS) + find_package(CURL 7.68.0 REQUIRED) + find_package(prometheus-cpp REQUIRED) # >= 1.0.0 +endif() + +### Other stuff that can be found out using find_package: + +# determine the thread library of the system find_package(Threads REQUIRED) + # some compilers need extra flags for std::filesystem, such as -lstdc++fs, this # produces a std::filesystem imported target that takes care of all these # details transparently find_package(Filesystem REQUIRED) -find_package(Date REQUIRED) -# Import some convenience functions -include(gkfs-utils) +# Search for 'source-only' dependencies +############################################################################### + +# the directory where external dependencies should be searched for +set(GKFS_DEPENDENCIES_PATH ${CMAKE_SOURCE_DIR}/external) + +### {fmt}: required for sensible output formatting +include_from_source(fmt + MESSAGE "[${PROJECT_NAME}] Searching for {fmt}" + SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt + GIT_TAG f94b7364b9409f05207c3af3fa4666730e11a854 # v6.1.2 +) + +# ensure that fmt is linked as PIC +set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) + +### spdlog: required for logging +include_from_source(spdlog + MESSAGE "[${PROJECT_NAME}] Searching for spdlog" + SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG eb3220622e73a4889eee355ffa37972b3cac3df5 # v1.9.2 + ) + +### CLI11: used for parsing command-line options +include_from_source(cli11 + MESSAGE "[${PROJECT_NAME}] Searching for CLI11" + SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/CLI11 + GIT_REPOSITORY https://github.com/CLIUtils/CLI11 + GIT_TAG v2.2.0 + ) + ################################################################################ -## Build GekkoFS documentation +## Check configured variables/options and act accordingly ################################################################################ -option(GKFS_BUILD_DOCUMENTATION "Build documentation" OFF) if(GKFS_BUILD_DOCUMENTATION) - add_subdirectory(docs) + add_subdirectory(docs) endif() -option(CREATE_CHECK_PARENTS "Check parent directory existance before creating child node" ON) -message(STATUS "[gekkofs] Create checks parents: ${CREATE_CHECK_PARENTS}") - -option(GKFS_SYMLINK_SUPPORT "Compile with support for symlinks" OFF) if (GKFS_SYMLINK_SUPPORT) add_definitions(-DHAS_SYMLINKS) endif () -option(GKFS_RENAME_SUPPORT "Compile with support for rename ops" OFF) + if (GKFS_RENAME_SUPPORT) # Rename depends on symlink support add_definitions(-DHAS_SYMLINKS) set(GKFS_SYMLINK_SUPPORT ON) add_definitions(-DHAS_RENAME) endif () -message(STATUS "[gekkofs] Symlink support: ${GKFS_SYMLINK_SUPPORT}") -message(STATUS "[gekkofs] Rename support: ${GKFS_RENAME_SUPPORT}") - -set(MAX_INTERNAL_FDS 256 CACHE STRING "Number of file descriptors reserved for internal use") -add_definitions(-DMAX_INTERNAL_FDS=${MAX_INTERNAL_FDS}) -message(STATUS "[gekkofs] File descriptors reserved for internal use: ${MAX_INTERNAL_FDS}") - -execute_process(COMMAND getconf OPEN_MAX - OUTPUT_VARIABLE GETCONF_MAX_FDS - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET) -if (NOT GETCONF_MAX_FDS) - set(GETCONF_MAX_FDS=512) -endif () -add_definitions(-DMAX_OPEN_FDS=${GETCONF_MAX_FDS}) -option(ENABLE_CLIENT_LOG "Enable logging messages" ON) -if (ENABLE_CLIENT_LOG) +if(MAX_INTERNAL_FDS) + add_definitions(-DMAX_INTERNAL_FDS=${MAX_INTERNAL_FDS}) +endif() + +if(MAX_OPEN_FDS) + add_definitions(-DMAX_OPEN_FDS=${MAX_OPEN_FDS}) +endif() + +if(ENABLE_CLIENT_LOG) add_definitions(-DGKFS_ENABLE_LOGGING) + add_definitions(-DLIBGKFS_LOG_MESSAGE_SIZE=${CLIENT_LOG_MESSAGE_SIZE}) endif () -message(STATUS "[gekkofs] Client logging output: ${ENABLE_CLIENT_LOG}") -option(GKFS_ENABLE_FORWARDING "Enable forwarding mode" OFF) -option(GKFS_ENABLE_AGIOS "Enable AGIOS scheduling library" OFF) -if (GKFS_ENABLE_AGIOS) - find_package(AGIOS REQUIRED) +if(GKFS_ENABLE_UNUSED_FUNCTIONS) + add_definitions(-DGKFS_ENABLE_UNUSED_FUNCTIONS) endif () -option(GKFS_ENABLE_UNUSED_FUNCTIONS "Enable unused functions compilation" OFF) - -option(GKFS_ENABLE_PARALLAX "Enable Parallax db backend" OFF) -option(GKFS_ENABLE_ROCKSDB "Enable ROCKSDB backend" ON) - -set(CLIENT_LOG_MESSAGE_SIZE 1024 CACHE STRING "Maximum size of a log message in the client library") -add_definitions(-DLIBGKFS_LOG_MESSAGE_SIZE=${CLIENT_LOG_MESSAGE_SIZE}) -message(STATUS "[gekkofs] Maximum log message size in the client library: ${CLIENT_LOG_MESSAGE_SIZE}") -mark_as_advanced(CLIENT_LOG_MESSAGE_SIZE) - -option(GKFS_USE_GUIDED_DISTRIBUTION "Use guided data distributor " OFF) -message(STATUS "[gekkofs] Guided data distributor: ${GKFS_USE_GUIDED_DISTRIBUTION}") - -if(GKFS_USE_GUIDED_DISTRIBUTION) - set(GKFS_USE_GUIDED_DISTRIBUTION_PATH "/tmp/guided.txt" CACHE STRING "File Path for guided distributor") - set_property(CACHE GKFS_USE_GUIDED_DISTRIBUTION_PATH PROPERTY STRINGS) - message(STATUS "[gekkofs] Guided data distributor input file path: ${GKFS_USE_GUIDED_DISTRIBUTION_PATH}") -endif() - -option(GKFS_ENABLE_PROMETHEUS "Enable Prometheus Push " OFF) if(GKFS_ENABLE_PROMETHEUS) add_definitions(-DGKFS_ENABLE_PROMETHEUS) endif () -message(STATUS "[gekkofs] Prometheus Output: ${GKFS_ENABLE_PROMETHEUS}") configure_file(include/common/cmake_configure.hpp.in include/common/cmake_configure.hpp) - - -add_library(CLI11 INTERFACE) -# we cannot use target_include_directories with CMake < 3.11 -set_target_properties(CLI11 - PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/external" - ) - -add_subdirectory(external/fmt) -set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) - -if (ENABLE_CLIENT_LOG) +if(ENABLE_CLIENT_LOG) option(HERMES_LOGGING "" ON) option(HERMES_LOGGING_FMT_USE_BUNDLED "" OFF) option(HERMES_LOGGING_FMT_HEADER_ONLY "" OFF) @@ -223,7 +297,7 @@ endif () option(HERMES_MARGO_COMPATIBLE_RPCS "" ON) add_subdirectory(external/hermes) -target_compile_definitions(hermes INTERFACE) +target_include_directories(hermes INTERFACE external/hermes/include) set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") @@ -233,22 +307,14 @@ include_directories( ${CMAKE_BINARY_DIR}/include ) -# Common components -add_subdirectory(src/common) -# Daemon -add_subdirectory(src/daemon) -# Client library -add_subdirectory(src/client) +add_subdirectory(src) +add_subdirectory(include) ### Mark any CMake variables imported from {fmt} and spdlog as advanced, so ### that they don't appear in cmake-gui or ccmake. Similarly for FETCHCONTENT ### variables. mark_variables_as_advanced(REGEX "^(FETCHCONTENT|fmt|FMT|spdlog|SPDLOG)_.*$") -option(GKFS_BUILD_TESTS "Build GekkoFS self tests" OFF) - -cmake_dependent_option(GKFS_INSTALL_TESTS "Install GekkoFS self tests" OFF "GKFS_BUILD_TESTS" OFF) - if (GKFS_BUILD_TESTS) # Boost preprocessor header-only is supplied by the Mercury installation find_package(Boost_preprocessor REQUIRED) @@ -292,3 +358,7 @@ else() unset(GKFS_TESTS_INTERFACE CACHE) endif() +################################################################################ +## Print GekkoFS configuration summary +################################################################################ +gkfs_feature_summary(DESCRIPTION "${PROJECT_NAME} configuration summary:") diff --git a/external/CLI11 b/external/CLI11 new file mode 160000 index 0000000000000000000000000000000000000000..b9be5b9444772324459989177108a6a65b8b2769 --- /dev/null +++ b/external/CLI11 @@ -0,0 +1 @@ +Subproject commit b9be5b9444772324459989177108a6a65b8b2769 diff --git a/external/CLI11/CLI11.hpp b/external/CLI11/CLI11.hpp deleted file mode 100644 index dcb57c6c6cafe03f7e5687e78e6cfba4f5a9ff8c..0000000000000000000000000000000000000000 --- a/external/CLI11/CLI11.hpp +++ /dev/null @@ -1,9066 +0,0 @@ -// CLI11: Version 2.1.2 -// Originally designed by Henry Schreiner -// https://github.com/CLIUtils/CLI11 -// -// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v2.1.2 -// -// CLI11 2.1.2 Copyright (c) 2017-2021 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 - - -#define CLI11_VERSION_MAJOR 2 -#define CLI11_VERSION_MINOR 1 -#define CLI11_VERSION_PATCH 2 -#define CLI11_VERSION "2.1.2" - - - - -// The following version macro is very similar to the one in pybind11 -#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) -#if __cplusplus >= 201402L -#define CLI11_CPP14 -#if __cplusplus >= 201703L -#define CLI11_CPP17 -#if __cplusplus > 201703L -#define CLI11_CPP20 -#endif -#endif -#endif -#elif defined(_MSC_VER) && __cplusplus == 199711L -// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is 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 -#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 - - - - -// C standard library -// Only needed for existence checking -#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 -#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 - -#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -#include // NOLINT(build/include) -#else -#include -#include -#endif - - - -namespace CLI { - - -/// Include the items in this namespace to get free conversion of enums to/from streams. -/// (This is available inside CLI as well, so CLI11 will use this without a using statement). -namespace enums { - -/// output streaming for enumerations -template ::value>::type> -std::ostream &operator<<(std::ostream &in, const T &item) { - // make sure this is out of the detail namespace otherwise it won't be found when needed - return in << static_cast::type>(item); -} - -} // namespace enums - -/// Export to CLI namespace -using enums::operator<<; - -namespace detail { -/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not -/// produce overflow for some expected uses -constexpr int expected_max_vector_size{1 << 29}; -// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c -/// Split a string by a delim -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; -} - -/// 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++; - } - return s.str(); -} - -/// 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 -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; -} - -/// Trim anything from left of string -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; -} - -/// Trim whitespace from right of string -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; -} - -/// Trim anything from right of string -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; -} - -/// 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 '\'' -inline std::string &remove_quotes(std::string &str) { - if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { - if(str.front() == str.back()) { - str.pop_back(); - str.erase(str.begin(), str.begin() + 1); - } - } - return str; -} - -/// 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. -inline std::string fix_newlines(const std::string &leader, std::string input) { - std::string::size_type n = 0; - while(n != std::string::npos && n < input.size()) { - n = input.find('\n', n); - if(n != std::string::npos) { - input = input.substr(0, n + 1) + leader + input.substr(n + 1); - n += leader.size(); - } - } - return input; -} - -/// 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 a two part "help" string -inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { - name = " " + name; - out << std::setw(static_cast(wid)) << std::left << name; - if(!description.empty()) { - if(name.length() >= wid) - out << "\n" << std::setw(static_cast(wid)) << ""; - for(const char c : description) { - out.put(c); - if(c == '\n') { - out << std::setw(static_cast(wid)) << ""; - } - } - } - out << "\n"; - return out; -} - -/// Print subcommand aliases -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; -} - -/// 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 != '-') && (c != '!') && (c != ' ') && c != '\n'); } - -/// Verify following characters of an option -template bool valid_later_char(T c) { - // = and : are value separators, { has special meaning for option defaults, - // and \n 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 != '{') && (c != ' ') && c != '\n'); -} - -/// Verify an option/subcommand name -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; -} - -/// Verify an app name -inline bool valid_alias_name_string(const std::string &str) { - static const std::string badChars(std::string("\n") + '\0'); - return (str.find_first_of(badChars) == std::string::npos); -} - -/// check if a string is a container segment separator (empty or "%%") -inline bool is_separator(const std::string &str) { - static const std::string sep("%%"); - return (str.empty() || str == sep); -} - -/// Verify that str consists of letters only -inline bool isalpha(const std::string &str) { - return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); -} - -/// Return a lower case version of a string -inline std::string to_lower(std::string str) { - std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { - return std::tolower(x, std::locale()); - }); - return str; -} - -/// remove underscores from a string -inline std::string remove_underscore(std::string str) { - str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); - return str; -} - -/// Find and replace a substring with another substring -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; -} - -/// check if the flag definitions has possible false flags -inline bool has_default_flag_values(const std::string &flags) { - return (flags.find_first_of("{!") != std::string::npos); -} - -inline 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()); -} - -/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores -inline std::ptrdiff_t find_member(std::string name, - const std::vector names, - bool ignore_case = false, - bool ignore_underscore = false) { - 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); -} - -/// 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; -} - -/// Split a string '"one two" "three"' into 'one two', 'three' -/// Quote characters can be ` ' or " -inline std::vector split_up(std::string str, char delimiter = '\0') { - - const std::string delims("\'\"`"); - auto find_ws = [delimiter](char ch) { - return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); - }; - trim(str); - - std::vector output; - bool embeddedQuote = false; - char keyChar = ' '; - while(!str.empty()) { - if(delims.find_first_of(str[0]) != std::string::npos) { - keyChar = str[0]; - auto end = str.find_first_of(keyChar, 1); - while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes - end = str.find_first_of(keyChar, end + 1); - embeddedQuote = true; - } - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); - if(end + 2 < str.size()) { - str = str.substr(end + 2); - } else { - str.clear(); - } - - } else { - output.push_back(str.substr(1)); - str = ""; - } - } 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 = ""; - } - } - // transform any embedded quotes into the regular character - if(embeddedQuote) { - output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); - embeddedQuote = false; - } - trim(str); - } - return output; -} - -/// 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. -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; -} - -/// Add quotes if the string contains spaces -inline std::string &add_quotes_if_needed(std::string &str) { - if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { - char quote = str.find('"') < str.find('\'') ? '\'' : '"'; - if(str.find(' ') != std::string::npos) { - str.insert(0, 1, quote); - str.append(1, quote); - } - } - return str; -} - -} // namespace detail - - - - -// Use one of these on all error classes. -// These are temporary and are undef'd at the end of this file. -#define CLI11_ERROR_DEF(parent, name) \ - protected: \ - name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ - name(std::string ename, std::string msg, ExitCodes exit_code) \ - : parent(std::move(ename), std::move(msg), exit_code) {} \ - \ - public: \ - name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ - name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} - -// This is added after the one above if a class is used directly and builds its own message -#define CLI11_ERROR_SIMPLE(name) \ - explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} - -/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, -/// int values from e.get_error_code(). -enum class ExitCodes { - Success = 0, - IncorrectConstruction = 100, - BadNameString, - OptionAlreadyAdded, - FileError, - ConversionError, - ValidationError, - RequiredError, - RequiresError, - ExcludesError, - ExtrasError, - ConfigError, - InvalidError, - HorribleError, - OptionNotFound, - ArgumentMismatch, - BaseClass = 127 -}; - -// Error definitions - -/// @defgroup error_group Errors -/// @brief Errors thrown by CLI11 -/// -/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. -/// @{ - -/// All errors derive from this one -class Error : public std::runtime_error { - int actual_exit_code; - std::string error_name{"Error"}; - - public: - int get_exit_code() const { return actual_exit_code; } - - 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 BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } - static BadNameString DashesOnly(std::string name) { - return BadNameString("Must have a name, not just dashes: " + 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 OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); - } - static OptionAlreadyAdded Excludes(std::string name, std::string other) { - return OptionAlreadyAdded(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 RequiredError("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 RequiredError("Exactly 1 option from [" + option_list + "] is required and " + 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 RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + - std::to_string(used) + "were given from [" + option_list + "]", - ExitCodes::RequiredError); - } - if(max_option == 1) - return RequiredError("Requires at most 1 options be given from [" + option_list + "]", - ExitCodes::RequiredError); - - return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + - 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"); - } -}; - -/// Thrown when a requires option is missing -class RequiresError : public ParseError { - CLI11_ERROR_DEF(ParseError, RequiresError) - RequiresError(std::string curname, std::string subname) - : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} -}; - -/// Thrown when an excludes option is present -class ExcludesError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExcludesError) - ExcludesError(std::string curname, std::string subname) - : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} -}; - -/// Thrown when too many positionals or options are found -class ExtrasError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExtrasError) - explicit ExtrasError(std::vector args) - : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " - : "The following argument was not expected: ") + - detail::rjoin(args, " "), - ExitCodes::ExtrasError) {} - ExtrasError(const std::string &name, std::vector args) - : ExtrasError(name, - (args.size() > 1 ? "The following arguments were not expected: " - : "The following argument was not expected: ") + - detail::rjoin(args, " "), - ExitCodes::ExtrasError) {} -}; - -/// Thrown when extra values are found in an INI file -class ConfigError : public ParseError { - CLI11_ERROR_DEF(ParseError, ConfigError) - CLI11_ERROR_SIMPLE(ConfigError) - static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } - static ConfigError NotConfigurable(std::string item) { - return ConfigError(item + ": This option is not allowed in a configuration file"); - } -}; - -/// Thrown when validation fails before parsing -class InvalidError : public ParseError { - CLI11_ERROR_DEF(ParseError, InvalidError) - explicit InvalidError(std::string name) - : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { - } -}; - -/// This is just a safety check to verify selection and parsing match - you should not ever see it -/// Strings are directly added to this error, but again, it should never be seen. -class HorribleError : public ParseError { - CLI11_ERROR_DEF(ParseError, HorribleError) - CLI11_ERROR_SIMPLE(HorribleError) -}; - -// After parsing - -/// Thrown when counting a non-existent option -class OptionNotFound : public Error { - CLI11_ERROR_DEF(Error, OptionNotFound) - explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} -}; - -#undef CLI11_ERROR_DEF -#undef CLI11_ERROR_SIMPLE - -/// @} - - - - -// Type tools - -// Utilities for type enabling -namespace detail { -// Based generally on https://rmf.io/cxx11/almost-static-if -/// Simple empty scoped class -enum class enabler {}; - -/// An instance to use in EnableIf -constexpr enabler dummy = {}; -} // namespace detail - -/// A copy of enable_if_t from C++14, compatible with C++11. -/// -/// We could check to see if C++14 is being used, but it does not hurt to redefine this -/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) -/// It is not in the std namespace anyway, so no harm done. -template using enable_if_t = typename std::enable_if::type; - -/// A copy of std::void_t from C++17 (helper for C++11 and C++14) -template struct make_void { using type = void; }; - -/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine -template using void_t = typename make_void::type; - -/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine -template using conditional_t = typename std::conditional::type; - -/// Check to see if something is bool (fail check by default) -template struct is_bool : std::false_type {}; - -/// Check to see if something is bool (true if actually a bool) -template <> struct is_bool : std::true_type {}; - -/// Check to see if something is a shared pointer -template struct is_shared_ptr : std::false_type {}; - -/// Check to see if something is a shared pointer (True if really a shared pointer) -template struct is_shared_ptr> : std::true_type {}; - -/// Check to see if something is a shared pointer (True if really a shared pointer) -template struct is_shared_ptr> : std::true_type {}; - -/// Check to see if something is copyable pointer -template struct is_copyable_ptr { - static bool const value = is_shared_ptr::value || std::is_pointer::value; -}; - -/// This can be specialized to override the type deduction for IsMember. -template struct IsMemberType { using type = T; }; - -/// The main custom type needed here is const char * should be a string. -template <> struct IsMemberType { using type = std::string; }; - -namespace 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__ -#pragma diag_suppress 2361 -#endif - TT { std::declval() } -#ifdef __CUDACC__ -#pragma diag_default 2361 -#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::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, a clear, and an 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_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 -template class is_tuple_like { - template - // 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; -}; - -/// 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(const T &value) { - return std::string(value); -} - -/// 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(); -} - -/// If conversion is not supported, return an empty string (streaming is not supported for that type) -template ::value && !is_ostreamable::value && - !is_readable_container::type>::value, - detail::enabler> = detail::dummy> -std::string to_string(T &&) { - return std::string{}; -} - -/// convert a readable container to a string -template ::value && !is_ostreamable::value && - is_readable_container::value, - detail::enabler> = detail::dummy> -std::string to_string(T &&variable) { - std::vector defaults; - auto cval = variable.begin(); - auto end = variable.end(); - while(cval != end) { - defaults.emplace_back(CLI::detail::to_string(*cval)); - ++cval; - } - return std::string("[" + detail::join(defaults) + "]"); -} - -/// 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; -}; - -/// 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{std::tuple_size::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}; -}; - -/// Set of overloads to get the type size of an object - -/// forward declare the subtype_count structure -template struct subtype_count; - -/// forward declare the subtype_count_min structure -template struct subtype_count_min; - -/// This will only trigger for actual void type -template struct type_count { static const int value{0}; }; - -/// Type size for regular object types that do not look like a tuple -template -struct type_count::value && !is_tuple_like::value && !is_complex::value && - !std::is_void::value>::type> { - static constexpr int value{1}; -}; - -/// Type size for complex since it sometimes looks like a wrapper -template struct type_count::value>::type> { - static constexpr int value{2}; -}; - -/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) -template struct type_count::value>::type> { - static constexpr int value{subtype_count::value}; -}; - -/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) -template -struct type_count::value && !is_complex::value && !is_tuple_like::value && - !is_mutable_container::value>::type> { - static constexpr int value{type_count::value}; -}; - -/// 0 if the index > tuple size -template -constexpr typename std::enable_if::value, int>::type tuple_type_size() { - return 0; -} - -/// Recursively generate the tuple type name -template - constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { - return subtype_count::type>::value + tuple_type_size(); -} - -/// Get the type size of the sum of type sizes for all the individual tuple types -template struct type_count::value>::type> { - static constexpr int value{tuple_type_size()}; -}; - -/// definition of subtype count -template struct subtype_count { - static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; -}; - -/// This will only trigger for actual void type -template struct type_count_min { static const int value{0}; }; - -/// Type size for regular object types that do not look like a tuple -template -struct type_count_min< - T, - typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && - !is_complex::value && !std::is_void::value>::type> { - static constexpr int value{type_count::value}; -}; - -/// Type size for complex since it sometimes looks like a wrapper -template struct type_count_min::value>::type> { - static constexpr int value{1}; -}; - -/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) -template -struct type_count_min< - T, - typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { - static constexpr int value{subtype_count_min::value}; -}; - -/// 0 if the index > tuple size -template -constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { - return 0; -} - -/// Recursively generate the tuple type name -template - constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { - return subtype_count_min::type>::value + tuple_type_size_min(); -} - -/// Get the type size of the sum of type sizes for all the individual tuple types -template struct type_count_min::value>::type> { - static constexpr int value{tuple_type_size_min()}; -}; - -/// definition of subtype count -template struct subtype_count_min { - static constexpr int value{is_mutable_container::value - ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) - : type_count_min::value}; -}; - -/// This will only trigger for actual void type -template struct expected_count { static const int value{0}; }; - -/// For most types the number of expected items is 1 -template -struct expected_count::value && !is_wrapper::value && - !std::is_void::value>::type> { - static constexpr int value{1}; -}; -/// number of expected items in a vector -template struct expected_count::value>::type> { - static constexpr int value{expected_max_vector_size}; -}; - -/// number of expected items in a vector -template -struct expected_count::value && is_wrapper::value>::type> { - static constexpr int value{expected_count::value}; -}; - -// Enumeration of the different supported categorizations of objects -enum class object_category : int { - char_value = 1, - integral_value = 2, - unsigned_integral = 4, - enumeration = 6, - boolean_value = 8, - floating_point = 10, - number_constructible = 12, - double_constructible = 14, - integer_constructible = 16, - // string like types - string_assignable = 23, - string_constructible = 24, - 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}; -}; - -/// String and similar direct assignment -template -struct classify_object::value && !std::is_integral::value && - 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) && - std::is_constructible::value>::type> { - static constexpr object_category value{object_category::string_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::value && !std::is_integral::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))>::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() { - std::string 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()) { - return false; - } - char *val = nullptr; - std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); - output = static_cast(output_ll); - return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; -} - -/// 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; - std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); - output = static_cast(output_ll); - return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; -} - -/// Convert a flag into an integer value typically binary flags -inline std::int64_t to_flag_value(std::string val) { - 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; - 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: - throw std::invalid_argument("unrecognized character"); - } - 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 { - ret = std::stoll(val); - } - return ret; -} - -/// Integer conversion -template ::value == object_category::integral_value || - classify_object::value == object_category::unsigned_integral, - detail::enabler> = detail::dummy> -bool lexical_cast(const std::string &input, T &output) { - return integral_conversion(input, output); -} - -/// char values -template ::value == object_category::char_value, detail::enabler> = detail::dummy> -bool lexical_cast(const std::string &input, T &output) { - if(input.size() == 1) { - output = static_cast(input[0]); - return true; - } - return integral_conversion(input, output); -} - -/// Boolean values -template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> -bool lexical_cast(const std::string &input, T &output) { - try { - auto out = to_flag_value(input); - output = (out > 0); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still - // valid all we care about the sign - output = (input[0] != '-'); - 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); - return val == (input.c_str() + input.size()); -} - -/// 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 = 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) { - 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; -} - -/// 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; - if(integral_conversion(input, val)) { - output = T(val); - return true; - } else { - double dval; - 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; - 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; - 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; - 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, - detail::enabler> = detail::dummy> -bool lexical_cast(const std::string &input, T &output) { - static_assert(is_istreamable::value, - "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 from_stream(input, output); -} - -/// 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), - 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, - 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); -} - -/// 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; - if(lexical_cast(input, val)) { - output = val; - 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 - typename std::remove_const::type>::type v1; - typename std::tuple_element<1, ConvertTo>::type v2; - bool retval = lexical_assign(strings[0], v1); - if(strings.size() > 1) { - retval = retval && lexical_assign(strings[1], 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()); - 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)); - } - 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 = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); - if(worked) { - output = ConvertTo{x, y}; - } - return worked; - } else { - 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::max)(mx_count, strings.size())}; - - while(index < mx) { - if(is_separator(strings[index])) { - break; - } - ++index; - } - bool retval = lexical_conversion( - std::vector(strings.begin(), strings.begin() + static_cast(index)), output); - strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); - 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 flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); - } - output = (count > 0) ? static_cast(count) : T{0}; -} - -/// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); - } - output = static_cast(count); -} - -#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 - -/// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value && !std::is_unsigned::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); - } - std::string out = detail::to_string(count); - lexical_cast(out, output); -} - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -} // namespace detail - - - -namespace detail { - -// Returns false if not a short option. Otherwise, sets opt name and rest and returns true -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; -} - -// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true -inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { - if(current.size() > 2 && current.substr(0, 2) == "--" && 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; -} - -// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true -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; -} - -// Splits a string into multiple long and short names -inline std::vector split_names(std::string current) { - std::vector output; - std::size_t val; - 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; -} - -/// extract default flag values either {def} or starting with a ! -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); - } - flag.erase(0, flag.find_first_not_of("-!")); - output.emplace_back(flag, defval); - } - return output; -} - -/// Get a vector of short names, one of long names, and a single name -inline std::tuple, std::vector, std::string> -get_names(const std::vector &input) { - - std::vector short_names; - std::vector long_names; - std::string pos_name; - - for(std::string name : input) { - if(name.length() == 0) { - continue; - } - if(name.length() > 1 && name[0] == '-' && name[1] != '-') { - if(name.length() == 2 && valid_first_char(name[1])) - short_names.emplace_back(1, name[1]); - else - 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 == "--") { - throw BadNameString::DashesOnly(name); - } else { - if(pos_name.length() > 0) - throw BadNameString::MultiPositionalNames(name); - pos_name = name; - } - } - - return std::tuple, std::vector, std::string>( - 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{}; - - /// The list of parents and name joined by "." - std::string fullname() const { - std::vector tmp = parents; - tmp.emplace_back(name); - return detail::join(tmp, "."); - } -}; - -/// 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 - virtual std::string to_flag(const ConfigItem &item) const { - if(item.inputs.size() == 1) { - return item.inputs.at(0); - } - throw ConversionError::TooManyInputsFlag(item.fullname()); - } - - /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure - std::vector from_file(const std::string &name) { - 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 - char characterQuote = '\''; - /// the maximum number of layers to allow - uint8_t maximumLayers{255}; - /// the separator used to separator parent layers - char parentSeparatorChar{'.'}; - /// 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 characters - ConfigBase *quoteCharacter(char qString, char qChar) { - stringQuote = qString; - characterQuote = qChar; - 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; - } - /// get a reference to the configuration section - std::string §ionRef() { return configSection; } - /// get the section - 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 - 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; - } -}; - -/// 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}; - - 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 { - std::string retstring; - if(active_) { - if(non_modifying_) { - std::string value = str; - retstring = func_(value); - } else { - retstring = func_(str); - } - } - return retstring; - } - - /// 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 - Validator description(std::string validator_desc) const { - Validator newval(*this); - newval.desc_function_ = [validator_desc]() { return validator_desc; }; - return newval; - } - /// Generate type description information for the Validator - 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 - Validator name(std::string validator_name) const { - Validator newval(*this); - newval.name_ = std::move(validator_name); - return newval; - } - /// Get the name of the Validator - 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 - 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 - 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 - int get_application_index() const { return application_index_; } - /// Get a boolean if the validator is active - 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 - 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 { - 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 + ")"; - else - return s1 + s2; - }; - - newval.active_ = (active_ & other.active_); - newval.application_index_ = application_index_; - return newval; - } - - /// 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 { - 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; - } - - /// Create a validator that fails when a given validator succeeds - 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; - } - - private: - void _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 CLI - -/// Class wrapping some of the accessors of Validator -class CustomValidator : public Validator { - public: -}; -// The implementation of the built in validators is using the Validator class; -// the user is only expected to use the const (static) versions (since there's no setup). -// Therefore, this is in detail. -namespace detail { - -/// CLI enumeration of different file types -enum class path_type { nonexistent, file, directory }; - -#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -/// get the type of the path from a file name -inline path_type check_path(const char *file) noexcept { - std::error_code ec; - auto stat = std::filesystem::status(file, ec); - if(ec) { - return path_type::nonexistent; - } - switch(stat.type()) { - case std::filesystem::file_type::none: - case std::filesystem::file_type::not_found: - return path_type::nonexistent; - 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 -/// get the type of the path from a file name -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 -/// Check for an existing file (returns error message if check fails) -class ExistingFileValidator : public Validator { - public: - 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(); - }; - } -}; - -/// Check for an existing directory (returns error message if check fails) -class ExistingDirectoryValidator : public Validator { - public: - 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(); - }; - } -}; - -/// Check for an existing path -class ExistingPathValidator : public Validator { - public: - 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(); - }; - } -}; - -/// Check for an non-existing path -class NonexistentPathValidator : public Validator { - public: - 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(); - }; - } -}; - -/// Validate the given string is a legal ipv4 address -class IPV4Validator : public Validator { - public: - IPV4Validator() : Validator("IPV4") { - func_ = [](std::string &ip_addr) { - auto result = CLI::detail::split(ip_addr, '.'); - if(result.size() != 4) { - return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; - } - int num; - for(const auto &var : result) { - bool retval = detail::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 - -// Static is not needed here, because global const implies static. - -/// Check for existing file (returns error message if check fails) -const detail::ExistingFileValidator ExistingFile; - -/// Check for an existing directory (returns error message if check fails) -const detail::ExistingDirectoryValidator ExistingDirectory; - -/// Check for an existing path -const detail::ExistingPathValidator ExistingPath; - -/// Check for an non-existing path -const detail::NonexistentPathValidator NonexistentPath; - -/// Check for an IP4 address -const detail::IPV4Validator ValidIPV4; - -/// Validate the input as a particular type -template class TypeValidator : public Validator { - public: - explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) { - func_ = [](std::string &input_string) { - auto val = DesiredType(); - if(!detail::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 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) { - T val; - bool converted = detail::lexical_cast(input, val); - if((!converted) || (val < min_val || val > max_val)) - return std::string("Value ") + input + " not in range " + std::to_string(min_val) + " to " + - std::to_string(max_val); - - 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() her is the smallest positive number -const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); - -/// Produce a bounded range (factory). Min and max are inclusive. -class Bound : public Validator { - public: - /// This bounds a value with min and max inclusive. - /// - /// Note that the constructor is templated, but the struct is not, so C++17 is not - /// needed to provide nice syntax for Range(a,b). - template Bound(T min_val, T max_val) { - std::stringstream out; - out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; - description(out.str()); - - func_ = [min_val, max_val](std::string &input) { - T val; - bool converted = detail::lexical_cast(input, val); - if(!converted) { - return std::string("Value ") + input + " could not be converted"; - } - if(val < min_val) - input = detail::to_string(min_val); - else if(val > max_val) - input = detail::to_string(max_val); - - return std::string{}; - }; - } - - /// Range of one value is 0 to value - template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} -}; - -namespace detail { -template ::type>::value, detail::enabler> = detail::dummy> -auto smart_deref(T value) -> decltype(*value) { - return *value; -} - -template < - typename T, - enable_if_t::type>::value, detail::enabler> = detail::dummy> -typename std::remove_reference::type &smart_deref(T &value) { - return value; -} -/// Generate a string representation of a set -template std::string generate_set(const T &set) { - using element_t = typename detail::element_type::type; - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair - std::string out(1, '{'); - out.append(detail::join( - detail::smart_deref(set), - [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, - ",")); - out.push_back('}'); - return out; -} - -/// Generate a string representation of a map -template std::string generate_map(const T &map, bool key_only = false) { - using element_t = typename detail::element_type::type; - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair - std::string out(1, '{'); - out.append(detail::join( - detail::smart_deref(map), - [key_only](const iteration_type_t &v) { - std::string res{detail::to_string(detail::pair_adaptor::first(v))}; - - if(!key_only) { - res.append("->"); - res += detail::to_string(detail::pair_adaptor::second(v)); - } - return res; - }, - ",")); - out.push_back('}'); - return out; -} - -template struct has_find { - template - static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); - template static auto test(...) -> decltype(std::false_type()); - - static const auto value = decltype(test(0))::value; - using type = std::integral_constant; -}; - -/// A search function -template ::value, detail::enabler> = detail::dummy> -auto search(const T &set, const V &val) -> std::pair { - using element_t = typename detail::element_type::type; - auto &setref = detail::smart_deref(set); - auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { - return (detail::pair_adaptor::first(v) == val); - }); - return {(it != std::end(setref)), it}; -} - -/// A search function that uses the built in find function -template ::value, detail::enabler> = detail::dummy> -auto search(const T &set, const V &val) -> std::pair { - auto &setref = detail::smart_deref(set); - auto it = setref.find(val); - return {(it != std::end(setref)), it}; -} - -/// A search function with a filter function -template -auto search(const T &set, const V &val, const std::function &filter_function) - -> std::pair { - using element_t = typename detail::element_type::type; - // do the potentially faster first search - auto res = search(set, val); - if((res.first) || (!(filter_function))) { - return res; - } - // if we haven't found it do the longer linear search with all the element translations - auto &setref = detail::smart_deref(set); - auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { - V a{detail::pair_adaptor::first(v)}; - a = filter_function(a); - return (a == val); - }); - return {(it != std::end(setref)), it}; -} - -// the following suggestion was made by Nikita Ofitserov(@himikof) -// done in templates to prevent compiler warnings on negation of unsigned numbers - -/// Do a check for overflow on signed numbers -template -inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { - if((a > 0) == (b > 0)) { - return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); - } else { - return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); - } -} -/// Do a check for overflow on unsigned numbers -template -inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { - return ((std::numeric_limits::max)() / a < b); -} - -/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. -template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { - if(a == 0 || b == 0 || a == 1 || b == 1) { - a *= b; - return true; - } - if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { - return false; - } - if(overflowCheck(a, b)) { - return false; - } - a *= b; - return true; -} - -/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. -template -typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { - T c = a * b; - if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { - return false; - } - a = c; - return true; -} - -} // namespace detail -/// Verify items are in a set -class IsMember : public Validator { - public: - using filter_fn_t = std::function; - - /// This allows in-place construction using an initializer list - template - IsMember(std::initializer_list values, Args &&...args) - : IsMember(std::vector(values), std::forward(args)...) {} - - /// This checks to see if an item is in a set (empty function) - template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} - - /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter - /// both sides of the comparison before computing the comparison. - template explicit IsMember(T set, F filter_function) { - - // Get the type of the contained item - requires a container have ::value_type - // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map - - using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones - // (const char * to std::string) - - // Make a local copy of the filter function, using a std::function if not one already - std::function filter_fn = filter_function; - - // This is the type name for help, it will take the current version of the set contents - desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; - - // This is the function that validates - // It stores a copy of the set pointer-like, so shared_ptr will stay alive - func_ = [set, filter_fn](std::string &input) { - local_item_t b; - if(!detail::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) { - local_item_t b; - if(!detail::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) { - local_item_t b; - bool converted = detail::lexical_cast(input, b); - if(converted) { - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(mapping, b, filter_fn); - if(res.first) { - input = detail::value_string(detail::pair_adaptor::second(*res.second)); - return std::string{}; - } - } - for(const auto &v : detail::smart_deref(mapping)) { - auto output_string = detail::value_string(detail::pair_adaptor::second(v)); - if(output_string == input) { - return std::string(); - } - } - - return "Check " + input + " " + tfunc() + " FAILED"; - }; - } - - /// You can pass in as many filter functions as you like, they nest - template - CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) - : CheckedTransformer( - std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} -}; - -/// Helper function to allow ignore_case to be passed to IsMember or Transform -inline std::string ignore_case(std::string item) { return detail::to_lower(item); } - -/// Helper function to allow ignore_underscore to be passed to IsMember or Transform -inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } - -/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform -inline std::string ignore_space(std::string item) { - item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); - item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); - return item; -} - -/// Multiply a number by a factor using given mapping. -/// Can be used to write transforms for SIZE or DURATION inputs. -/// -/// Example: -/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` -/// one can recognize inputs like "100", "12kb", "100 MB", -/// that will be automatically transformed to 100, 14448, 104857600. -/// -/// Output number type matches the type in the provided mapping. -/// Therefore, if it is required to interpret real inputs like "0.42 s", -/// the mapping should be of a type or . -class AsNumberWithUnit : public Validator { - public: - /// Adjust AsNumberWithUnit behavior. - /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. - /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError - /// if UNIT_REQUIRED is set and unit literal is not found. - enum Options { - CASE_SENSITIVE = 0, - CASE_INSENSITIVE = 1, - UNIT_OPTIONAL = 0, - UNIT_REQUIRED = 2, - DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL - }; - - template - explicit AsNumberWithUnit(std::map mapping, - Options opts = DEFAULT, - const std::string &unit_name = "UNIT") { - description(generate_description(unit_name, opts)); - validate_mapping(mapping, opts); - - // transform function - func_ = [mapping, opts](std::string &input) -> std::string { - Number num; - - detail::rtrim(input); - if(input.empty()) { - throw ValidationError("Input is empty"); - } - - // Find split position between number and prefix - auto unit_begin = input.end(); - while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { - --unit_begin; - } - - std::string unit{unit_begin, input.end()}; - input.resize(static_cast(std::distance(input.begin(), unit_begin))); - detail::trim(input); - - if(opts & UNIT_REQUIRED && unit.empty()) { - throw ValidationError("Missing mandatory unit"); - } - if(opts & CASE_INSENSITIVE) { - unit = detail::to_lower(unit); - } - if(unit.empty()) { - if(!detail::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()) { - bool converted = detail::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(); - } -}; - -/// 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) : AsNumberWithUnit(get_mapping(kb_is_1000)) { - if(kb_is_1000) { - description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); - } else { - description("SIZE [b, kb(=1024b), ...]"); - } - } - - private: - /// Get mapping - static std::map 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; - } - - /// Cache calculated mapping - static std::map get_mapping(bool kb_is_1000) { - if(kb_is_1000) { - static auto m = init_mapping(true); - return m; - } else { - static auto m = init_mapping(false); - return m; - } - } -}; - -namespace detail { -/// Split a string into a program name and command line arguments -/// the string is assumed to contain a file name followed by other arguments -/// the return value contains is a pair with the first argument containing the program name and the second -/// everything else. -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 != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; - ltrim(vals.second); - return vals; -} - -} // namespace detail -/// @} - - - - -class Option; -class App; - -/// This enum signifies the type of help requested -/// -/// This is passed in by App; all user classes must accept this as -/// the second argument. - -enum class AppFormatMode { - Normal, ///< The normal, detailed help - All, ///< A fully expanded help - Sub, ///< Used when printed as part of expanded subcommand -}; - -/// This is the minimum requirements to run a formatter. -/// -/// A user can subclass this is if they do not care at all -/// about the structure in CLI::Formatter. -class FormatterBase { - protected: - /// @name Options - ///@{ - - /// The width of the first column - std::size_t column_width_{30}; - - /// @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; - - /// Adding a destructor in this form to work around bug in GCC 4.7 - virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) - - /// This is the key method that puts together help - virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; - - ///@} - /// @name Setters - ///@{ - - /// Set the "REQUIRED" label - void label(std::string key, std::string val) { labels_[key] = val; } - - /// Set the column width - void column_width(std::size_t val) { column_width_ = val; } - - ///@} - /// @name Getters - ///@{ - - /// Get the current value of a name (REQUIRED, etc.) - std::string get_label(std::string key) const { - if(labels_.find(key) == labels_.end()) - return key; - else - return labels_.at(key); - } - - /// Get the current column width - std::size_t get_column_width() const { return column_width_; } - - ///@} -}; - -/// This is a specialty override for lambda functions -class FormatterLambda final : public FormatterBase { - using funct_t = std::function; - - /// The lambda to hold and run - funct_t lambda_; - - public: - /// Create a FormatterLambda with a lambda function - explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} - - /// Adding a destructor (mostly to make GCC 4.7 happy) - ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) - - /// This will simply call the lambda function - std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { - return lambda_(app, name, mode); - } -}; - -/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few -/// overridable methods, to be highly customizable with minimal effort. -class Formatter : public FormatterBase { - public: - Formatter() = default; - Formatter(const Formatter &) = default; - Formatter(Formatter &&) = default; - - /// @name Overridables - ///@{ - - /// This prints out a group of options with title - /// - 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) 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) const override; - - ///@} - /// @name Options - ///@{ - - /// This prints out an option help line, either positional or optional form - virtual std::string make_option(const Option *opt, bool is_positional) const { - std::stringstream out; - detail::format_help( - out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); - return out.str(); - } - - /// @brief This is the name part of an option, Default: left column - virtual std::string make_option_name(const Option *, bool) const; - - /// @brief This is the options part of the name, Default: combined into left column - virtual std::string make_option_opts(const Option *) const; - - /// @brief This is the description. Default: Right column, on new line if left column too large - virtual std::string make_option_desc(const Option *) const; - - /// @brief This is used to print the name on the USAGE line - virtual std::string make_option_usage(const Option *opt) const; - - ///@} -}; - - - - -using results_t = std::vector; -/// callback function definition -using callback_t = std::function; - -class Option; -class App; - -using Option_p = std::unique_ptr