From 3c3894389721d339e70c9996ab528e8795d3f723 Mon Sep 17 00:00:00 2001 From: Stephane Del Pino <stephane.delpino44@gmail.com> Date: Sun, 8 Apr 2018 14:25:40 +0200 Subject: [PATCH] git subrepo pull packages/CLI11 subrepo: subdir: "packages/CLI11" merged: "4f6bbba3" upstream: origin: "git@github.com:CLIUtils/CLI11.git" branch: "master" commit: "4f6bbba3" git-subrepo: version: "0.3.1" origin: "git@github.com:ingydotnet/git-subrepo.git" commit: "a7ee886" --- packages/CLI11/.appveyor.yml | 11 +- packages/CLI11/.ci/build_cmake.sh | 17 --- packages/CLI11/.ci/build_doxygen.sh | 5 + packages/CLI11/.ci/build_lcov.sh | 4 + packages/CLI11/.ci/check_tidy.sh | 16 ++- packages/CLI11/.ci/make_and_test.sh | 20 +++ packages/CLI11/.ci/prepare_altern.sh | 18 --- packages/CLI11/.ci/run_codecov.sh | 17 ++- packages/CLI11/.ci/travis.sh | 20 --- packages/CLI11/.gitrepo | 4 +- packages/CLI11/.travis.yml | 115 +++++++++-------- packages/CLI11/CHANGELOG.md | 30 +++++ packages/CLI11/CMakeLists.txt | 40 +++--- packages/CLI11/README.md | 17 ++- packages/CLI11/cmake/AddGoogletest.cmake | 54 +++++--- packages/CLI11/conanfile.py | 4 +- packages/CLI11/docs/Doxyfile | 2 +- packages/CLI11/examples/CMakeLists.txt | 2 +- packages/CLI11/examples/enum.cpp | 14 ++- packages/CLI11/include/CLI/App.hpp | 43 ++++--- packages/CLI11/include/CLI/CLI.hpp | 4 + packages/CLI11/include/CLI/Error.hpp | 5 +- packages/CLI11/include/CLI/Macros.hpp | 44 +++++++ packages/CLI11/include/CLI/Option.hpp | 118 +++++++++++++----- packages/CLI11/include/CLI/Optional.hpp | 78 ++++++++++++ packages/CLI11/include/CLI/TypeTools.hpp | 10 +- packages/CLI11/scripts/MakeSingleHeader.py | 114 ++++++++++++----- packages/CLI11/tests/AppTest.cpp | 136 ++++++++++++++++----- packages/CLI11/tests/CMakeLists.txt | 46 +++++-- packages/CLI11/tests/CreationTest.cpp | 21 +++- packages/CLI11/tests/HelpTest.cpp | 13 +- packages/CLI11/tests/HelpersTest.cpp | 4 +- packages/CLI11/tests/NewParseTest.cpp | 24 ++-- packages/CLI11/tests/OptionalTest.cpp | 31 +++++ packages/CLI11/tests/SimpleTest.cpp | 2 +- packages/CLI11/tests/app_helper.hpp | 2 +- packages/CLI11/tests/informational.cpp | 50 ++++++++ 37 files changed, 843 insertions(+), 312 deletions(-) delete mode 100644 packages/CLI11/.ci/build_cmake.sh create mode 100755 packages/CLI11/.ci/make_and_test.sh delete mode 100644 packages/CLI11/.ci/prepare_altern.sh delete mode 100755 packages/CLI11/.ci/travis.sh create mode 100644 packages/CLI11/include/CLI/Macros.hpp create mode 100644 packages/CLI11/include/CLI/Optional.hpp create mode 100644 packages/CLI11/tests/OptionalTest.cpp create mode 100644 packages/CLI11/tests/informational.cpp diff --git a/packages/CLI11/.appveyor.yml b/packages/CLI11/.appveyor.yml index 6d4f5b5e6..91a95a011 100644 --- a/packages/CLI11/.appveyor.yml +++ b/packages/CLI11/.appveyor.yml @@ -1,6 +1,6 @@ branches: - except: - - gh-pages + only: + - master install: - set PATH=C:\Python36;%PATH% @@ -12,13 +12,14 @@ install: build_script: - mkdir build - cd build - - cmake .. -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_GENERATOR="Visual Studio 14 2015" - - cmake --build . + - ps: cmake .. -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_GENERATOR="Visual Studio 14 2015" + - ps: cmake --build . - cd .. - conan create . CLIUtils/CLI11 test_script: - - ctest --output-on-failure -C Debug + - cd build + - ps: ctest --output-on-failure -C Debug notifications: - provider: Webhook diff --git a/packages/CLI11/.ci/build_cmake.sh b/packages/CLI11/.ci/build_cmake.sh deleted file mode 100644 index 5b2d69bb6..000000000 --- a/packages/CLI11/.ci/build_cmake.sh +++ /dev/null @@ -1,17 +0,0 @@ -CMAKE_VERSION=3.9.6 -CMAKE_MVERSION=${CMAKE_VERSION%.*} -# Non Bash version: -# echo CMAKE_MVERSION=`$var | awk -F"." '{ print $1"."$2 }'` - -if [ "$TRAVIS_OS_NAME" = "linux" ] ; then CMAKE_URL="https://cmake.org/files/v${CMAKE_MVERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.tar.gz" ; fi -if [ "$TRAVIS_OS_NAME" = "osx" ] ; then CMAKE_URL="https://cmake.org/files/v$CMAKE_MVERSION/cmake-$CMAKE_VERSION-Darwin-x86_64.tar.gz" ; fi -cd "${DEPS_DIR}" - -if [[ ! -f "${DEPS_DIR}/cmake/bin/cmake" ]] ; then - echo "Downloading CMake $CMAKE_VERSION" - mkdir -p cmake - travis_retry wget --no-check-certificate --quiet -O - "${CMAKE_URL}" | tar --strip-components=1 -xz -C cmake -fi - -export PATH="${DEPS_DIR}/cmake/bin:${PATH}" -cd "${TRAVIS_BUILD_DIR}" diff --git a/packages/CLI11/.ci/build_doxygen.sh b/packages/CLI11/.ci/build_doxygen.sh index 5087f2be1..6772ecf97 100644 --- a/packages/CLI11/.ci/build_doxygen.sh +++ b/packages/CLI11/.ci/build_doxygen.sh @@ -1,3 +1,5 @@ +set -evx + DOXYGEN_URL="ftp://ftp.stack.nl/pub/users/dimitri/doxygen-1.8.13.src.tar.gz" cd "${DEPS_DIR}" @@ -15,3 +17,6 @@ fi export PATH="${DEPS_DIR}/doxygen/build/bin:${PATH}" cd "${TRAVIS_BUILD_DIR}" + +set +evx + diff --git a/packages/CLI11/.ci/build_lcov.sh b/packages/CLI11/.ci/build_lcov.sh index 3b89dd33c..8671d7881 100644 --- a/packages/CLI11/.ci/build_lcov.sh +++ b/packages/CLI11/.ci/build_lcov.sh @@ -1,3 +1,5 @@ +set -evx + LCOV_URL="http://ftp.de.debian.org/debian/pool/main/l/lcov/lcov_1.13.orig.tar.gz" cd "${DEPS_DIR}" @@ -9,3 +11,5 @@ fi export PATH="${DEPS_DIR}/lcov/bin:${PATH}" cd "${TRAVIS_BUILD_DIR}" + +set +evx diff --git a/packages/CLI11/.ci/check_tidy.sh b/packages/CLI11/.ci/check_tidy.sh index 55189d9e4..505b68eb9 100755 --- a/packages/CLI11/.ci/check_tidy.sh +++ b/packages/CLI11/.ci/check_tidy.sh @@ -1,11 +1,21 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash + +echo -en "travis_fold:start:script.build\\r" +echo "Building with tidy on..." set -evx -mkdir build-tidy || true +mkdir -p build-tidy cd build-tidy -CXX_FLAGS="-Werror -Wall -Wextra -pedantic -std=c++11" cmake .. -DCLANG_TIDY_FIX=ON +CXX_FLAGS="-Werror -Wcast-align -Wfloat-equal -Wimplicit-atomic-properties -Wmissing-declarations -Woverlength-strings -Wshadow -Wstrict-selector-match -Wundeclared-selector -Wunreachable-code -std=c++11" cmake .. -DCLANG_TIDY_FIX=ON cmake --build . +set -evx +echo -en "travis_fold:end:script.build\\r" +echo -en "travis_fold:start:script.compare\\r" +echo "Checking git diff..." +set -evx + git diff --exit-code --color set +evx +echo -en "travis_fold:end:script.compare\\r" diff --git a/packages/CLI11/.ci/make_and_test.sh b/packages/CLI11/.ci/make_and_test.sh new file mode 100755 index 000000000..2ba81aeef --- /dev/null +++ b/packages/CLI11/.ci/make_and_test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +echo -en "travis_fold:start:script.build\\r" +echo "Building..." +set -evx + +mkdir -p build +cd build +cmake .. -DCLI11_CXX_STD=$1 -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache +cmake --build . -- -j2 + +set +evx +echo -en "travis_fold:end:script.build\\r" +echo -en "travis_fold:start:script.test\\r" +echo "Testing..." +set -evx + +ctest --output-on-failure + +set +evx +echo -en "travis_fold:end:script.test\\r" diff --git a/packages/CLI11/.ci/prepare_altern.sh b/packages/CLI11/.ci/prepare_altern.sh deleted file mode 100644 index e384b0034..000000000 --- a/packages/CLI11/.ci/prepare_altern.sh +++ /dev/null @@ -1,18 +0,0 @@ -cd "${DEPS_DIR}" -mkdir -p extrabin -cd extrabin - -if [ "$CXX" = "g++" ] ; then - ln -s `which gcc-$COMPILER` gcc - ln -s `which g++-$COMPILER` g++ - ln -s `which gcov-$COMPILER` gcov -else - ln -s `which clang-$COMPILER` clang - ln -s `which clang++-$COMPILER` clang++ - ln -s `which clang-format-$COMPILER` clang-format - ln -s `which clang-tidy-$COMPILER` clang-tidy -fi - -export PATH="${DEPS_DIR}/extrabin":$PATH - -cd "${TRAVIS_BUILD_DIR}" diff --git a/packages/CLI11/.ci/run_codecov.sh b/packages/CLI11/.ci/run_codecov.sh index cd7219a8c..28d149a53 100755 --- a/packages/CLI11/.ci/run_codecov.sh +++ b/packages/CLI11/.ci/run_codecov.sh @@ -1,14 +1,27 @@ +#!/usr/bin/env bash + +echo -en "travis_fold:start:script.build\\r" +echo "Building..." set -evx cd ${TRAVIS_BUILD_DIR} mkdir -p build cd build -cmake .. -DCLI_SINGLE_FILE_TESTS=OFF -DCLI_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Coverage +cmake .. -DCLI11_SINGLE_FILE_TESTS=OFF -DCLI11_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Coverage cmake --build . -- -j2 -cmake --build . --target CLI_coverage +cmake --build . --target CLI11_coverage + +set +evx +echo -en "travis_fold:end:script.build\\r" +echo -en "travis_fold:start:script.lcov\\r" +echo "Capturing and uploading LCov..." +set -evx lcov --directory . --capture --output-file coverage.info # capture coverage info lcov --remove coverage.info '*/tests/*' '*/examples/*' '*gtest*' '*gmock*' '/usr/*' --output-file coverage.info # filter out system lcov --list coverage.info #debug info # Uploading report to CodeCov bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" + +set +evx +echo -en "travis_fold:end:script.lcov\\r" diff --git a/packages/CLI11/.ci/travis.sh b/packages/CLI11/.ci/travis.sh deleted file mode 100755 index 49cc2f8ea..000000000 --- a/packages/CLI11/.ci/travis.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env sh -set -evx - -mkdir build || true -cd build -cmake .. -DCLI_CXX_STD=11 -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -cmake --build . -- -j2 -ctest --output-on-failure -if [ -n "$CLI_CXX_STD" ] && [ "$CLI_CXX_STD" -ge "14" ] ; then - cmake .. -DCLI_CXX_STD=14 -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug - cmake --build . -- -j2 - ctest --output-on-failure -fi -if [ -n "$CLI_CXX_STD" ] && [ "$CLI_CXX_STD" -ge "17" ] ; then - cmake .. -DCLI_CXX_STD=17 -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug - cmake --build . -- -j2 - ctest --output-on-failure -fi - -set +evx diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 11c78bd36..8e84b44c4 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,6 +6,6 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = master - commit = b4b7d991f69add2e4fb007d1df4c14a088127d9b - parent = 8546076c470f8af8014418e169819302cd7d1f66 + commit = 4f6bbba3170dc9b1f0c671298e12d8b49c160ce0 + parent = 4326875f2ca41888cc0384b68d2a90324b7580cc cmdver = 0.3.1 diff --git a/packages/CLI11/.travis.yml b/packages/CLI11/.travis.yml index c02c44b06..872aa39f9 100644 --- a/packages/CLI11/.travis.yml +++ b/packages/CLI11/.travis.yml @@ -1,82 +1,98 @@ language: cpp sudo: false dist: trusty + +# Exclude ghpages, +# but even better, don't build branch and PR, just PR branches: - exclude: - - gh-pages + only: + - master + cache: + ccache: true + apt: true directories: - - "${TRAVIS_BUILD_DIR}/deps/cmake" - "${TRAVIS_BUILD_DIR}/deps/doxygen" + matrix: include: + # Default clang - compiler: clang - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-5.0 - packages: - - clang++-5.0 - env: - - COMPILER=5.0 - - CLI_CXX_STD=14 - - compiler: clang - addons: - apt: - packages: - - clang-3.9 - - clang-format-3.9 - - clang-tidy-3.9 + script: + - .ci/make_and_test.sh 11 + - .ci/make_and_test.sh 14 + - .ci/make_and_test.sh 17 + + # Check style/tidy + - compiler: clang env: - - COMPILER=3.9 - - CLI_CXX_STD=14 + - CHECK_STYLE=yes script: - cd "${TRAVIS_BUILD_DIR}" - scripts/check_style.sh - - ".ci/check_tidy.sh" + - .ci/check_tidy.sh + + # Docs and clang 3.5 - compiler: clang + env: + - DEPLOYMAT=yes addons: apt: packages: - clang-3.5 - env: - - COMPILER=3.5 - - DEPLOY_MAT=yes - - DOXYFILE=$TRAVIS_BUILD_DIR/docs/Doxyfile + install: + - export CC=clang-3.5 + - export CXX=clang++-3.5 + script: + - .ci/make_and_test.sh 11 after_success: + - export DOXYFILE=$TRAVIS_BUILD_DIR/docs/Doxyfile - | if [ "${TRAVIS_BRANCH}" == "master" ] && [ "${TRAVIS_PULL_REQUEST}" == "false" ] then - echo "Updating docs" && cd $TRAVIS_BUILD_DIR && .ci/build_docs.sh + . .ci/build_doxygen.sh + .ci/build_docs.sh fi + + # GCC 6 and Coverage - compiler: gcc + env: + - GCC_VER=7 addons: apt: sources: - ubuntu-toolchain-r-test packages: - - g++-6 + - g++-7 - curl - lcov - env: - - COMPILER=6 - - CLI_CXX_STD=14 - before_install: + install: + - export CC=gcc-7 + - export CXX=g++-7 - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" - cd $TRAVIS_BUILD_DIR - ". .ci/build_lcov.sh" - ".ci/run_codecov.sh" + script: + - .ci/make_and_test.sh 11 + - .ci/make_and_test.sh 14 + - .ci/make_and_test.sh 17 + + # GCC 4.7 and Conan - compiler: gcc + env: + - GCC_VER=4.7 addons: apt: packages: - g++-4.7 - env: - - COMPILER=4.7 - before_install: + install: + - export CC=gcc-4.7 + - export CXX=g++-4.7 - python -m pip install --user conan - conan user + script: + - .ci/make_and_test.sh 11 after_success: - conan create . CLIUtils/stable - | @@ -86,33 +102,28 @@ matrix: conan user -p ${BINFROG_API_KEY} -r origin henryiii conan upload "*" -c -r origin --all fi + + # macOS and clang - os: osx compiler: clang - before_install: + install: - brew update - echo 'brew "python"' > Brewfile - echo 'brew "conan"' >> Brewfile + - echo 'brew "ccache"' >> Brewfile - brew bundle - python -m ensurepip --user - conan user after_success: - conan create . CLIUtils/CLI11 -install: -- python -c 'import sys; print(sys.version_info[:])' -- DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" -- if [ "$TRAVIS_OS_NAME" = "linux" ]; then cd $TRAVIS_BUILD_DIR && . .ci/prepare_altern.sh - ; fi -- if [ "$TRAVIS_OS_NAME" = "linux" ] ; then cd $TRAVIS_BUILD_DIR && . .ci/build_cmake.sh - ; fi -- if [ "$TRAVIS_OS_NAME" = "linux" ] ; then cd $TRAVIS_BUILD_DIR && . .ci/build_doxygen.sh - ; fi -- cd "${DEPS_DIR}" -- if [ "$(python -c 'import sys; print(sys.version_info[0])')" = "2" ] ; then python - -m pip install --user pathlib ; fi -- cmake --version + +install: skip + script: -- cd "${TRAVIS_BUILD_DIR}" -- ".ci/travis.sh" +- .ci/make_and_test.sh 11 +- .ci/make_and_test.sh 14 + + deploy: provider: releases api_key: diff --git a/packages/CLI11/CHANGELOG.md b/packages/CLI11/CHANGELOG.md index 279ebe327..f1f52fe82 100644 --- a/packages/CLI11/CHANGELOG.md +++ b/packages/CLI11/CHANGELOG.md @@ -1,3 +1,33 @@ +## In progress + +This version has some internal cleanup and improved support for the newest compilers. + +Note: This is the final release with `requires`, please switch to `needs`. + +* Fix unlimited short options eating two values before checking for positionals when no space present [#90] +* Symmetric exclude text when excluding options, exclude can be called multiple times [#64] +* Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95] +* All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95] +* The internal stream was not being cleared before use in some cases. Fixed. [#95] +* Using an emum now requires explicit conversion overload [#97] + +Other, non-user facing changes: + +* Added `Macros.hpp` with better C++ mode discovery [#95] +* Deprecated macros added for all platforms +* C++17 is now tested on supported platforms [#95] +* Informational printout now added to CTest [#95] +* Better single file generation [#95] +* Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next version of GTest) +* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92] + +[#64]: https://github.com/CLIUtils/CLI11/issues/64 +[#90]: https://github.com/CLIUtils/CLI11/issues/90 +[#92]: https://github.com/CLIUtils/CLI11/issues/92 +[#95]: https://github.com/CLIUtils/CLI11/pull/95 +[#97]: https://github.com/CLIUtils/CLI11/pull/97 + + ## Version 1.4: More feedback This version adds lots of smaller fixes and additions after the refactor in version 1.3. More ways to download and use CLI11 in CMake have been added. INI files have improved support. diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index f31547e3b..040ae0e00 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -16,10 +16,10 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) # Only if built as the main project if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) # User settable - set(CLI_CXX_STD "11" CACHE STRING "The CMake standard to require") + set(CLI11_CXX_STD "11" CACHE STRING "The CMake standard to require") set(CUR_PROJ ON) - set(CMAKE_CXX_STANDARD ${CLI_CXX_STD}) + set(CMAKE_CXX_STANDARD ${CLI11_CXX_STD}) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -27,7 +27,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(MSVC) add_definitions("/W4") else() - add_definitions("-Wall -Wextra -pedantic") + add_definitions(-Wall -Wextra -pedantic -Wno-deprecated-declarations) endif() if(CMAKE_VERSION VERSION_GREATER 3.6) @@ -51,15 +51,18 @@ else() set(CUR_PROJ OFF) endif() +# Allow dependent options +include(CMakeDependentOption) + # Allow IDE's to group targets into folders set_property(GLOBAL PROPERTY USE_FOLDERS ON) if(CMAKE_BUILD_TYPE STREQUAL Coverage) include(CodeCoverage) - setup_target_for_coverage(CLI_coverage ctest coverage) + setup_target_for_coverage(CLI11_coverage ctest coverage) endif() -file(GLOB CLI_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*") +file(GLOB CLI11_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*") # To see in IDE, must be listed for target add_library(CLI11 INTERFACE) @@ -112,19 +115,14 @@ export(PACKAGE CLI11) # Single file test find_package(PythonInterp) -if(CUR_PROJ AND PYTHONINTERP_FOUND) - set(CLI_SINGLE_FILE_DEFAULT ON) -else() - set(CLI_SINGLE_FILE_DEFAULT OFF) -endif() +cmake_dependent_option(CLI11_SINGLE_FILE "Generate a single header file" ON "CUR_PROJ;PYTHONINTERP_FOUND" OFF) -option(CLI_SINGLE_FILE "Generate a single header file" ${CLI_SINGLE_FILE_DEFAULT}) -if(CLI_SINGLE_FILE) +if(CLI11_SINGLE_FILE) find_package(PythonInterp REQUIRED) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI_headers} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers} ) add_custom_target(generate_cli_single_file ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") @@ -134,27 +132,23 @@ if(CLI_SINGLE_FILE) add_library(CLI11_SINGLE INTERFACE) target_link_libraries(CLI11_SINGLE INTERFACE CLI11) add_dependencies(CLI11_SINGLE generate_cli_single_file) - target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI_SINGLE_FILE) + target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE) target_include_directories(CLI11_SINGLE INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/include/") endif() -option(CLI_SINGLE_FILE_TESTS "Duplicate all the tests for a single file build" OFF) +option(CLI11_SINGLE_FILE_TESTS "Duplicate all the tests for a single file build" OFF) -option(CLI_TESTING "Build the tests and add them" ${CUR_PROJ}) -if(CLI_TESTING) +cmake_dependent_option(CLI11_TESTING "Build the tests and add them" ON "CUR_PROJ" OFF) +if(CLI11_TESTING) enable_testing() add_subdirectory(tests) endif() -option(CLI_EXAMPLES "Build the examples" ${CUR_PROJ}) -if(CLI_EXAMPLES) +cmake_dependent_option(CLI11_EXAMPLES "Build the examples" ON "CUR_PROJ" OFF) +if(CLI11_EXAMPLES) add_subdirectory(examples) endif() -if(NOT CUR_PROJ) - mark_as_advanced(CLI_SINGLE_FILE_TESTS CLI_EXAMPLES CLI_TESTING) -endif() - # Packaging support set(CPACK_PACKAGE_VENDOR "github.com/CLIUtils/CLI11") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Command line interface") diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index f2c2cf0a1..437208ae9 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -63,7 +63,7 @@ After I wrote this, I also found the following libraries: | [Argh!] | Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free.| | [CLI] | Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active. | -See [Awesome C++] for a less-biased list of parsers. +See [Awesome C++] for a less-biased list of parsers. You can also find other single file libraries at [Single file libs]. </p></details> <br/> @@ -164,6 +164,8 @@ An option name must start with a alphabetic character or underscore. For long op On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. +On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL` before including CLI11 to manually add support for `boost::optional`. See [CLI11 Internals] for information on how this was done and how you can add your own converters. + ### Example * `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` @@ -305,7 +307,11 @@ Also, in a related note, the `App` you get a pointer to is stored in the parent ## How it works Every `add_` option you have seen so far depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of strings, so it knows how many times an option was passed or how many arguments it received (flags add empty strings to keep the counts correct). The lambda returns `true` if it could validate the option strings, and -`false` if it failed. If you wanted to extend this to support a new type, just use a lambda. An example of a new parser for `complex<double>` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: +`false` if it failed. + +Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add an enum, for example, provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`). + +If you wanted to extend this to support a completely new type, just use a lambda. An example of a new parser for `complex<double>` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: ### Example @@ -421,6 +427,7 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 [University of Cincinnati]: http://www.uc.edu [GitBook]: https://cliutils.gitlab.io/CLI11Tutorial +[CLI11 Internals]: https://cliutils.gitlab.io/CLI11Tutorial/chapters/internals.html [ProgramOptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx [Argument Aggregator]: https://github.com/vietjtnguyen/argagg [Args]: https://github.com/Taywee/args @@ -434,5 +441,7 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [wandbox-link]: https://wandbox.org/permlink/g7tRkuU8xY3aTIVP [releases-badge]: https://img.shields.io/github/release/CLIUtils/CLI11.svg [cli11-po-compare]: https://iscinumpy.gitlab.io/post/comparing-cli11-and-boostpo/ -[DIANA slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf -[Awesome C++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli +[DIANA slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf +[Awesome C++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli +[CLI]: https://codesynthesis.com/projects/cli/ +[Single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md diff --git a/packages/CLI11/cmake/AddGoogletest.cmake b/packages/CLI11/cmake/AddGoogletest.cmake index 80eb9ed6e..e6bf162ae 100644 --- a/packages/CLI11/cmake/AddGoogletest.cmake +++ b/packages/CLI11/cmake/AddGoogletest.cmake @@ -4,24 +4,38 @@ # gives output on failed tests without having to set an environment variable. # # -set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF) -include(DownloadProject) -download_project(PROJ googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.8.0 - UPDATE_DISCONNECTED 1 - QUIET -) +if(CMAKE_VERSION VERSION_LESS 3.11) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") + include(DownloadProject) + download_project(PROJ googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0 + UPDATE_DISCONNECTED 1 + QUIET + ) + + # CMake warning suppression will not be needed in version 1.9 + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL) +else() + include(FetchContent) + FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0) + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() +endif() -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -# CMake warning suppression will not be needed in version 1.9 -set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") -add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL) -unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS) -if (CMAKE_CONFIGURATION_TYPES) +if(CMAKE_CONFIGURATION_TYPES) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure --build-config "$<CONFIGURATION>") @@ -54,16 +68,17 @@ macro(add_gtest TESTNAME) gtest_add_tests(TARGET ${TESTNAME} TEST_PREFIX "${TESTNAME}." TEST_LIST TmpTestList) + set_tests_properties(${TmpTestList} PROPERTIES FOLDER "Tests") else() gtest_discover_tests(${TESTNAME} TEST_PREFIX "${TESTNAME}." - ) + PROPERTIES FOLDER "Tests") endif() else() add_test(${TESTNAME} ${TESTNAME}) + set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") endif() - set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") endmacro() @@ -80,3 +95,10 @@ BUILD_GTEST set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES FOLDER "Extern") + +if(MSVC AND MSVC_VERSION GREATER_EQUAL 1900) + target_compile_definitions(gtest PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) + target_compile_definitions(gtest_main PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) + target_compile_definitions(gmock PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) + target_compile_definitions(gmock_main PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) +endif() diff --git a/packages/CLI11/conanfile.py b/packages/CLI11/conanfile.py index 77e584f9a..7a32f711d 100644 --- a/packages/CLI11/conanfile.py +++ b/packages/CLI11/conanfile.py @@ -22,8 +22,8 @@ class HelloConan(ConanFile): def build(self): # this is not building a library, just tests cmake = CMake(self) - cmake.definitions["CLI_EXAMPLES"] = "OFF" - cmake.definitions["CLI_SINGLE_FILE"] = "OFF" + cmake.definitions["CLI11_EXAMPLES"] = "OFF" + cmake.definitions["CLI11_SINGLE_FILE"] = "OFF" cmake.configure() cmake.build() cmake.test() diff --git a/packages/CLI11/docs/Doxyfile b/packages/CLI11/docs/Doxyfile index 5363a26e1..0e2a8c75b 100644 --- a/packages/CLI11/docs/Doxyfile +++ b/packages/CLI11/docs/Doxyfile @@ -334,7 +334,7 @@ BUILTIN_STL_SUPPORT = NO # enable parsing support. # The default value is: NO. -CPP_CLI_SUPPORT = NO +CPP_CLI11_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen diff --git a/packages/CLI11/examples/CMakeLists.txt b/packages/CLI11/examples/CMakeLists.txt index c2eef50cc..8c2f13b3c 100644 --- a/packages/CLI11/examples/CMakeLists.txt +++ b/packages/CLI11/examples/CMakeLists.txt @@ -1,5 +1,5 @@ function(add_cli_exe T) - add_executable(${T} ${ARGN} ${CLI_headers}) + add_executable(${T} ${ARGN} ${CLI11_headers}) target_link_libraries(${T} PUBLIC CLI11) set_target_properties( ${T} PROPERTIES diff --git a/packages/CLI11/examples/enum.cpp b/packages/CLI11/examples/enum.cpp index 827bee392..7018681fa 100644 --- a/packages/CLI11/examples/enum.cpp +++ b/packages/CLI11/examples/enum.cpp @@ -1,12 +1,22 @@ +#include <sstream> #include <CLI/CLI.hpp> -enum Level : std::int32_t { High, Medium, Low }; +enum class Level : int { High, Medium, Low }; + +std::istream &operator>>(std::istream &in, Level &level) { + int i; + in >> i; + level = static_cast<Level>(i); + return in; +} + +std::ostream &operator<<(std::ostream &in, const Level &level) { return in << static_cast<int>(level); } int main(int argc, char **argv) { CLI::App app; Level level; - app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings") + app.add_set("-l,--level", level, {Level::High, Level::Medium, Level::Low}, "Level settings") ->set_type_name("enum/Level in {High=0, Medium=1, Low=2}"); CLI11_PARSE(app, argc, argv); diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index 8fd85e470..543b85a19 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -19,6 +19,7 @@ // CLI Library includes #include "CLI/Error.hpp" #include "CLI/Ini.hpp" +#include "CLI/Macros.hpp" #include "CLI/Option.hpp" #include "CLI/Split.hpp" #include "CLI/StringTools.hpp" @@ -196,6 +197,9 @@ class App { set_help_flag("-h,--help", "Print this help message and exit"); } + /// virtual destructor + virtual ~App() = default; + /// Set a callback for the end of parsing. /// /// Due to a bug in c++11, @@ -447,7 +451,7 @@ class App { return opt; } -#if __cplusplus >= 201402L +#ifdef CLI11_CPP14 /// Add option for callback (C++14 or better only) Option *add_flag(std::string name, std::function<void(size_t)> function, ///< A function to call, void(size_t) @@ -834,7 +838,7 @@ class App { std::string value; // Non-flags - if(opt->get_expected() != 0) { + if(opt->get_type_size() != 0) { // If the option was found on command line if(opt->count() > 0) @@ -1048,10 +1052,10 @@ class App { /// This returns the number of remaining options, minus the -- seperator size_t remaining_size(bool recurse = false) const { - size_t count = std::count_if( + size_t count = static_cast<size_t>(std::count_if( std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifer, std::string> &val) { return val.first != detail::Classifer::POSITIONAL_MARK; - }); + })); if(recurse) { for(const App_p &sub : subcommands_) { count += sub->remaining_size(recurse); @@ -1068,7 +1072,7 @@ class App { /// Currently checks to see if multiple positionals exist with -1 args void _validate() const { auto count = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { - return opt->get_expected() == -1 && opt->get_positional(); + return opt->get_items_expected() < 0 && opt->get_positional(); }); if(count > 1) throw InvalidError(name_); @@ -1188,8 +1192,8 @@ class App { // Required or partially filled if(opt->get_required() || opt->count() != 0) { // Make sure enough -N arguments parsed (+N is already handled in parsing function) - if(opt->get_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_expected())) - throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_expected()); + if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected())) + throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_items_expected()); // Required but empty if(opt->get_required() && opt->count() == 0) @@ -1259,7 +1263,7 @@ class App { if(op->results_.empty()) { // Flag parsing - if(op->get_expected() == 0) { + if(op->get_type_size() == 0) { if(current.inputs.size() == 1) { std::string val = current.inputs.at(0); val = detail::to_lower(val); @@ -1319,9 +1323,9 @@ class App { size_t _count_remaining_positionals(bool required = false) const { size_t retval = 0; for(const Option_p &opt : options_) - if(opt->get_positional() && (!required || opt->get_required()) && opt->get_expected() > 0 && - static_cast<int>(opt->count()) < opt->get_expected()) - retval = static_cast<size_t>(opt->get_expected()) - opt->count(); + if(opt->get_positional() && (!required || opt->get_required()) && opt->get_items_expected() > 0 && + static_cast<int>(opt->count()) < opt->get_items_expected()) + retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); return retval; } @@ -1333,7 +1337,7 @@ class App { for(const Option_p &opt : options_) { // Eat options, one by one, until done if(opt->get_positional() && - (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { + (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { opt->add_result(positional); parse_order_.push_back(opt.get()); @@ -1420,27 +1424,34 @@ class App { // Get a reference to the pointer to make syntax bearable Option_p &op = *op_ptr; - int num = op->get_expected(); + int num = op->get_items_expected(); + // Make sure we always eat the minimum for unlimited vectors + int collected = 0; + + // --this=value if(!value.empty()) { - if(num != -1) + // If exact number expected + if(num > 0) num--; op->add_result(value); parse_order_.push_back(op.get()); + collected += 1; } else if(num == 0) { op->add_result(""); parse_order_.push_back(op.get()); + // -Trest } else if(!rest.empty()) { if(num > 0) num--; op->add_result(rest); parse_order_.push_back(op.get()); rest = ""; + collected += 1; } // Unlimited vector parser if(num < 0) { - int collected = 0; // Make sure we always eat the minimum while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { if(collected >= -num) { // We could break here for allow extras, but we don't @@ -1451,7 +1462,7 @@ class App { // If there are any unlimited positionals, those also take priority if(std::any_of(std::begin(options_), std::end(options_), [](const Option_p &opt) { - return opt->get_positional() && opt->get_expected() < 0; + return opt->get_positional() && opt->get_items_expected() < 0; })) break; } diff --git a/packages/CLI11/include/CLI/CLI.hpp b/packages/CLI11/include/CLI/CLI.hpp index 53a0cef23..662e833dd 100644 --- a/packages/CLI11/include/CLI/CLI.hpp +++ b/packages/CLI11/include/CLI/CLI.hpp @@ -8,6 +8,10 @@ #include "CLI/Version.hpp" +#include "CLI/Macros.hpp" + +#include "CLI/Optional.hpp" + #include "CLI/StringTools.hpp" #include "CLI/Error.hpp" diff --git a/packages/CLI11/include/CLI/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index bd47806b0..09104a651 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -91,6 +91,9 @@ class IncorrectConstruction : public ConstructionError { 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"); } @@ -102,7 +105,7 @@ class IncorrectConstruction : public ConstructionError { return IncorrectConstruction("Option " + name + " is not defined"); } static IncorrectConstruction MultiOptionPolicy(std::string name) { - return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options"); + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); } }; diff --git a/packages/CLI11/include/CLI/Macros.hpp b/packages/CLI11/include/CLI/Macros.hpp new file mode 100644 index 000000000..9c10b0814 --- /dev/null +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -0,0 +1,44 @@ +#pragma once + +// Distributed under the 3-Clause BSD License. See accompanying +// file LICENSE or https://github.com/CLIUtils/CLI11 for details. + +namespace CLI { + +// Note that all code in CLI11 must be in a namespace, even if it just a define. + +// 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(PYBIND11_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 + +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index f8222d349..dd61dfd9d 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -13,6 +13,7 @@ #include <vector> #include "CLI/Error.hpp" +#include "CLI/Macros.hpp" #include "CLI/Split.hpp" #include "CLI/StringTools.hpp" @@ -179,11 +180,13 @@ class Option : public OptionBase<Option> { /// @name Configuration ///@{ - /// The number of expected values, 0 for flag, -1 for unlimited vector - int expected_{1}; + /// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option, + /// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean + /// vector of pairs. + int type_size_{1}; - /// A private setting to allow args to not be able to accept incorrect expected values - bool changeable_{false}; + /// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values. + int expected_{1}; /// A list of validators to run on each value parsed std::vector<std::function<std::string(std::string &)>> validators_; @@ -243,14 +246,25 @@ class Option : public OptionBase<Option> { /// @name Setting options ///@{ - /// Set the number of expected arguments (Flags bypass this) + /// Set the number of expected arguments (Flags don't use this) Option *expected(int value) { - if(expected_ == value) - return this; + // Break if this is a flag + if(type_size_ == 0) + throw IncorrectConstruction::SetFlag(single_name()); + + // Setting 0 is not allowed else if(value == 0) throw IncorrectConstruction::Set0Opt(single_name()); - else if(!changeable_) + + // No change is okay, quit now + else if(expected_ == value) + return this; + + // Type must be a vector + else if(type_size_ >= 0) throw IncorrectConstruction::ChangeNotVector(single_name()); + + // TODO: Can support multioption for non-1 values (except for join) else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) throw IncorrectConstruction::AfterMultiOpt(single_name()); @@ -299,25 +313,36 @@ class Option : public OptionBase<Option> { return needs(opt1, args...); } -#if __cplusplus <= 201703L +#ifndef CLI11_CPP20 /// Sets required options \deprecated + CLI11_DEPRECATED("Use needs instead of requires (eventual keyword clash)") Option *requires(Option *opt) { return needs(opt); } /// Can find a string if needed \deprecated - template <typename T = App> Option *requires(std::string opt_name) { return needs<T>(opt_name); } + template <typename T = App> Option *requires(std::string opt_name) { + for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) + if(opt.get() != this && opt->check_name(opt_name)) + return needs(opt.get()); + throw IncorrectConstruction::MissingOption(opt_name); + } /// Any number supported, any mix of string and Opt \deprecated template <typename A, typename B, typename... ARG> Option *requires(A opt, B opt1, ARG... args) { - needs(opt); - return needs(opt1, args...); + requires(opt); + return requires(opt1, args...); } #endif /// Sets excluded options Option *excludes(Option *opt) { - auto tup = excludes_.insert(opt); - if(!tup.second) - throw OptionAlreadyAdded::Excludes(get_name(), opt->get_name()); + excludes_.insert(opt); + + // Help text should be symmetric - excluding a should exclude b + opt->excludes_.insert(this); + + // Ignoring the insert return value, excluding twice is now allowed. + // (Mostly to allow both directions to be excluded by user, even though the library does it for you.) + return this; } @@ -356,9 +381,10 @@ class Option : public OptionBase<Option> { return this; } - /// Take the last argument if given multiple times + /// Take the last argument if given multiple times (or another policy) Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { - if(get_expected() != 0 && get_expected() != 1) + + if(get_items_expected() < 0) throw IncorrectConstruction::MultiOptionPolicy(single_name()); multi_option_policy_ = value; return this; @@ -369,8 +395,32 @@ class Option : public OptionBase<Option> { ///@{ /// The number of arguments the option expects + int get_type_size() const { return type_size_; } + + /// The number of times the option expects to be included int get_expected() const { return expected_; } + /// \breif The total number of expected values (including the type) + /// This is positive if exactly this number is expected, and negitive for at least N values + /// + /// v = fabs(size_type*expected) + /// !MultiOptionPolicy::Throw + /// | Expected < 0 | Expected == 0 | Expected > 0 + /// Size < 0 | -v | 0 | -v + /// Size == 0 | 0 | 0 | 0 + /// Size > 0 | -v | 0 | -v // Expected must be 1 + /// + /// MultiOptionPolicy::Throw + /// | Expected < 0 | Expected == 0 | Expected > 0 + /// Size < 0 | -v | 0 | v + /// Size == 0 | 0 | 0 | 0 + /// Size > 0 | v | 0 | v // Expected must be 1 + /// + int get_items_expected() const { + return std::abs(type_size_ * expected_) * + ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); + } + /// True if this has a default value int get_default() const { return default_; } @@ -416,7 +466,7 @@ class Option : public OptionBase<Option> { return out; } - /// The most discriptive name available + /// The most descriptive name available std::string single_name() const { if(!lnames_.empty()) return std::string("--") + lnames_[0]; @@ -444,7 +494,7 @@ class Option : public OptionBase<Option> { std::string help_aftername() const { std::stringstream out; - if(get_expected() != 0) { + if(get_type_size() != 0) { if(!typeval_.empty()) out << " " << typeval_; if(!defaultval_.empty()) @@ -488,20 +538,29 @@ class Option : public OptionBase<Option> { bool local_result; + // Num items expected or length of vector, always at least 1 + // Only valid for a trimming policy + int trim_size = std::min(std::max(std::abs(get_items_expected()), 1), static_cast<int>(results_.size())); + // Operation depends on the policy setting if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { - results_t partial_result = {results_.back()}; + // Allow multi-option sizes (including 0) + results_t partial_result{results_.end() - trim_size, results_.end()}; local_result = !callback_(partial_result); + } else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) { - results_t partial_result = {results_.at(0)}; + results_t partial_result{results_.begin(), results_.begin() + trim_size}; local_result = !callback_(partial_result); + } else if(multi_option_policy_ == MultiOptionPolicy::Join) { results_t partial_result = {detail::join(results_, "\n")}; local_result = !callback_(partial_result); + } else { - if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) || - (expected_ < 0 && results_.size() < static_cast<size_t>(-expected_))) - throw ArgumentMismatch(single_name(), expected_, results_.size()); + // For now, vector of non size 1 types are not supported but possibility included here + if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) || + (get_items_expected() < 0 && results_.size() < static_cast<size_t>(-get_items_expected()))) + throw ArgumentMismatch(single_name(), get_items_expected(), results_.size()); else local_result = !callback_(results_); } @@ -583,13 +642,14 @@ class Option : public OptionBase<Option> { /// @name Custom options ///@{ - /// Set a custom option, typestring, expected; locks changeable unless expected is -1 - void set_custom_option(std::string typeval, int expected = 1) { + /// Set a custom option, typestring, type_size + void set_custom_option(std::string typeval, int type_size = 1) { typeval_ = typeval; - expected_ = expected; - if(expected == 0) + type_size_ = type_size; + if(type_size_ == 0) required_ = false; - changeable_ = expected < 0; + if(type_size < 0) + expected_ = -1; } /// Set the default value string representation diff --git a/packages/CLI11/include/CLI/Optional.hpp b/packages/CLI11/include/CLI/Optional.hpp new file mode 100644 index 000000000..1597da336 --- /dev/null +++ b/packages/CLI11/include/CLI/Optional.hpp @@ -0,0 +1,78 @@ +#pragma once + +// Distributed under the 3-Clause BSD License. See accompanying +// file LICENSE or https://github.com/CLIUtils/CLI11 for details. + +#include <istream> + +#include "CLI/Macros.hpp" + +// [CLI11:verbatim] +#ifdef __has_include +#if defined(CLI11_CPP17) && __has_include(<optional>) +#include <optional> +#ifdef __cpp_lib_optional +#ifndef CLI11_STD_OPTIONAL +#define CLI11_STD_OPTIONAL +#endif +#endif +#endif +#if defined(CLI11_CPP14) && __has_include(<experimental/optional>) +#include <experimental/optional> +#ifndef CLI11_EXPERIMENTAL_OPTIONAL +#define CLI11_EXPERIMENTAL_OPTIONAL +#endif +#endif +#if __has_include(<boost/optional.hpp>) +#include <boost/optional.hpp> +#ifndef CLI11_BOOST_OPTIONAL +#define CLI11_BOOST_OPTIONAL +#endif +#endif +#endif +// [CLI11:verbatim] + +namespace CLI { + +#ifdef CLI11_STD_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#ifdef CLI11_EXPERIMENTAL_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#ifdef CLI11_BOOST_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if defined(CLI11_STD_OPTIONAL) +using std::optional; +#elif defined(CLI11_EXPERIMENTAL_OPTIONAL) +using std::experimental::optional; +#elif defined(CLI11_BOOST_OPTIONAL) +using boost::optional; +#endif + +// This is true if any optional is found +#if defined(CLI11_STD_OPTIONAL) || defined(CLI11_EXPERIMENTAL_OPTIONAL) || defined(CLI11_BOOST_OPTIONAL) +#define CLI11_OPTIONAL +#endif + +} // namespace CLI diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index fecdbafd2..aea4b8568 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -74,8 +74,7 @@ constexpr const char *type_name() { /// Signed integers / enums template <typename T, - enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value) || std::is_enum<T>::value, - detail::enabler> = detail::dummy> + enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; @@ -124,7 +123,7 @@ bool lexical_cast(std::string input, T &output) { /// String and similar template <typename T, - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value && + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && std::is_assignable<T &, std::string>::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { @@ -134,17 +133,18 @@ bool lexical_cast(std::string input, T &output) { /// Non-string parsable template <typename T, - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value && + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_assignable<T &, std::string>::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { // On GCC 4.7, thread_local is not available, so this optimization -// is turned off (avoiding multiple initialisations on multiple usages +// is turned off (avoiding multiple initialisations on multiple usages) #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && __GNUC__ == 4 && (__GNUC_MINOR__ < 8) std::istringstream is; #else static thread_local std::istringstream is; + is.clear(); #endif is.str(input); diff --git a/packages/CLI11/scripts/MakeSingleHeader.py b/packages/CLI11/scripts/MakeSingleHeader.py index e6985beb1..d260ab959 100755 --- a/packages/CLI11/scripts/MakeSingleHeader.py +++ b/packages/CLI11/scripts/MakeSingleHeader.py @@ -1,66 +1,114 @@ #!/usr/bin/env python -# Requires pathlib on python 2 - from __future__ import print_function, unicode_literals import os import re import argparse +import operator +from copy import copy from subprocess import check_output, CalledProcessError +from functools import reduce includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE) includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE) +verbatim_tag_str = r""" +^ # Begin of line +[^\n^\[]+ # Some characters, not including [ or the end of a line +\[ # A literal [ +[^\]^\n]* # Anything except a closing ] +CLI11:verbatim # The tag +[^\]^\n]* # Anything except a closing ] +\] # A literal ] +[^\n]* # Up to end of line +$ # End of a line +""" +verbatim_all = re.compile(verbatim_tag_str + "(.*)" + verbatim_tag_str, + re.MULTILINE | re.DOTALL | re.VERBOSE) + +DIR = os.path.dirname(os.path.abspath(__file__)) + +class HeaderFile(object): + TAG = "Unknown git revision" + + def __init__(self, base, inc): + with open(os.path.join(base, inc)) as f: + inner = f.read() -DIR = os.path.dirname(os.path.abspath(__file__)) # Path(__file__).resolve().parent -BDIR = os.path.join(os.path.dirname(DIR), 'include') # DIR.parent / 'include' + # add self.verbatim + if 'CLI11:verbatim' in inner: + self.verbatim = ["\n\n// Verbatim copy from {}".format(inc)] + self.verbatim += verbatim_all.findall(inner) + inner = verbatim_all.sub("", inner) + else: + self.verbatim = [] -print("Git directory:", DIR) + self.headers = set(includes_system.findall(inner)) -try: - TAG = check_output(['git', 'describe', '--tags', '--always'], cwd=str(DIR)).decode("utf-8") -except CalledProcessError: - TAG = "A non-git source" + self.body = '\n// From {}\n\n'.format(inc) + inner[inner.find('namespace'):] -def MakeHeader(out): - main_header = os.path.join(BDIR, 'CLI', 'CLI.hpp') - with open(main_header) as f: - header = f.read() + def __add__(self, other): + out = copy(self) + out.headers |= other.headers + out.body += other.body + out.verbatim += other.verbatim + return out - include_files = includes_local.findall(header) + @property + def header_str(self): + return '\n'.join('#include <'+h+'>' for h in sorted(self.headers)) - headers = set() - output = '' - for inc in include_files: - with open(os.path.join(BDIR, inc)) as f: - inner = f.read() - headers |= set(includes_system.findall(inner)) - output += '\n// From {inc}\n\n'.format(inc=inc) - output += inner[inner.find('namespace'):] - - header_list = '\n'.join('#include <'+h+'>' for h in headers) + @property + def verbatim_str(self): + return '\n'.join(self.verbatim) - output = '''\ + def __str__(self): + return '''\ #pragma once // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/CLIUtils/CLI11 for details. // This file was generated using MakeSingleHeader.py in CLI11/scripts -// from: {tag} +// from: {self.TAG} // This has the complete CLI library in one file. -{header_list} -{output}'''.format(header_list=header_list, output=output, tag=TAG) +{self.header_str} +{self.verbatim_str} +{self.body} +'''.format(self=self) + + +def MakeHeader(output, main_header, include_dir = '../include'): + # Set tag if possible to class variable + try: + HeaderFile.TAG = check_output(['git', 'describe', '--tags', '--always'], cwd=str(DIR)).decode("utf-8") + except CalledProcessError: + pass - with open(out, 'w') as f: - f.write(output) + base_dir = os.path.abspath(os.path.join(DIR, include_dir)) + main_header = os.path.join(base_dir, main_header) - print("Created {out}".format(out=out)) + with open(main_header) as f: + header = f.read() + + include_files = includes_local.findall(header) + + headers = [HeaderFile(base_dir, inc) for inc in include_files] + single_header = reduce(operator.add, headers) + + with open(output, 'w') as f: + f.write(str(single_header)) + + print("Created", output) if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument("output", nargs='?', default=os.path.join(BDIR, 'CLI11.hpp')) + parser.add_argument("output", help="Single header file output") + parser.add_argument("--main", default='CLI/CLI.hpp', help="The main include file that defines the other files") + parser.add_argument("--include", default='../include') args = parser.parse_args() - MakeHeader(args.output) + + MakeHeader(args.output, args.main, args.include) + diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index e53f9a135..0b1c24164 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -1,5 +1,6 @@ #include "app_helper.hpp" #include <cstdlib> +#include <complex> TEST_F(TApp, OneFlagShort) { app.add_flag("-c,--count"); @@ -290,6 +291,40 @@ TEST_F(TApp, JoinOpt2) { EXPECT_EQ(str, "one\ntwo"); } +TEST_F(TApp, TakeLastOptMulti) { + std::vector<int> vals; + app.add_option("--long", vals)->expected(2)->take_last(); + + args = {"--long", "1", "2", "3"}; + + run(); + + EXPECT_EQ(vals, std::vector<int>({2, 3})); +} + +TEST_F(TApp, TakeFirstOptMulti) { + std::vector<int> vals; + app.add_option("--long", vals)->expected(2)->take_first(); + + args = {"--long", "1", "2", "3"}; + + run(); + + EXPECT_EQ(vals, std::vector<int>({1, 2})); +} + +TEST_F(TApp, ComplexOptMulti) { + std::complex<double> val; + app.add_complex("--long", val)->take_first(); + + args = {"--long", "1", "2", "3", "4"}; + + run(); + + EXPECT_DOUBLE_EQ(val.real(), 1); + EXPECT_DOUBLE_EQ(val.imag(), 2); +} + TEST_F(TApp, MissingValueNonRequiredOpt) { int count; app.add_option("-c,--count", count); @@ -318,6 +353,23 @@ TEST_F(TApp, MissingValueMoreThan) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } +TEST_F(TApp, NoMissingValueMoreThan) { + std::vector<int> vals1; + std::vector<int> vals2; + app.add_option("-v", vals1)->expected(-2); + app.add_option("--vals", vals2)->expected(-2); + + args = {"-v", "2", "3", "4"}; + run(); + EXPECT_EQ(vals1, std::vector<int>({2, 3, 4})); + + app.reset(); + + args = {"--vals", "2", "3", "4"}; + run(); + EXPECT_EQ(vals2, std::vector<int>({2, 3, 4})); +} + TEST_F(TApp, NotRequiredOptsSingle) { std::string str; @@ -415,6 +467,52 @@ TEST_F(TApp, RequiredOptsDoubleNeg) { EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); } +// This makes sure unlimited option priority is +// correct for space vs. no space #90 +TEST_F(TApp, PositionalNoSpace) { + std::vector<std::string> options; + std::string foo, bar; + + app.add_option("-O", options); + app.add_option("foo", foo)->required(); + app.add_option("bar", bar)->required(); + + args = {"-O", "Test", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); + + app.reset(); + args = {"-OTest", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); +} + +TEST_F(TApp, PositionalNoSpaceLong) { + std::vector<std::string> options; + std::string foo, bar; + + app.add_option("--option", options); + app.add_option("foo", foo)->required(); + app.add_option("bar", bar)->required(); + + args = {"--option", "Test", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); + + app.reset(); + args = {"--option=Test", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); +} + TEST_F(TApp, RequiredOptsUnlimited) { std::vector<std::string> strs; @@ -545,26 +643,6 @@ TEST_F(TApp, NotRequiedExpectedDoubleShort) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } -TEST_F(TApp, EnumTest) { - enum Level : std::int32_t { High, Medium, Low }; - Level level = Level::Low; - app.add_option("--level", level); - - args = {"--level", "1"}; - run(); - EXPECT_EQ(level, Level::Medium); -} - -TEST_F(TApp, NewEnumTest) { - enum class Level2 : std::int32_t { High, Medium, Low }; - Level2 level = Level2::Low; - app.add_option("--level", level); - - args = {"--level", "1"}; - run(); - EXPECT_EQ(level, Level2::Medium); -} - TEST_F(TApp, RequiredFlags) { app.add_flag("-a")->required(); app.add_flag("-b")->mandatory(); // Alternate term @@ -587,24 +665,24 @@ TEST_F(TApp, RequiredFlags) { TEST_F(TApp, CallbackFlags) { - int value = 0; + size_t value = 0; auto func = [&value](size_t x) { value = x; }; app.add_flag_function("-v", func); run(); - EXPECT_EQ(value, 0); + EXPECT_EQ(value, (size_t)0); app.reset(); args = {"-v"}; run(); - EXPECT_EQ(value, 1); + EXPECT_EQ(value, (size_t)1); app.reset(); args = {"-vv"}; run(); - EXPECT_EQ(value, 2); + EXPECT_EQ(value, (size_t)2); EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); } @@ -612,24 +690,24 @@ TEST_F(TApp, CallbackFlags) { #if __cplusplus >= 201402L TEST_F(TApp, CallbackFlagsAuto) { - int value = 0; + size_t value = 0; auto func = [&value](size_t x) { value = x; }; app.add_flag("-v", func); run(); - EXPECT_EQ(value, 0); + EXPECT_EQ(value, (size_t)0); app.reset(); args = {"-v"}; run(); - EXPECT_EQ(value, 1); + EXPECT_EQ(value, (size_t)1); app.reset(); args = {"-vv"}; run(); - EXPECT_EQ(value, 2); + EXPECT_EQ(value, (size_t)2); EXPECT_THROW(app.add_flag("hi", func), CLI::IncorrectConstruction); } @@ -1115,7 +1193,7 @@ TEST_F(TApp, NeedsMixedFlags) { run(); } -#if __cplusplus <= 201703L +#ifndef CLI11_CPP20 TEST_F(TApp, RequiresMixedFlags) { CLI::Option *opt1 = app.add_flag("--opt1"); diff --git a/packages/CLI11/tests/CMakeLists.txt b/packages/CLI11/tests/CMakeLists.txt index e2ae6d2c8..4c191fcc4 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -1,7 +1,7 @@ -set(GOOGLE_TEST_INDIVIDUAL ON) +set(GOOGLE_TEST_INDIVIDUAL OFF) include(AddGoogletest) -set(CLI_TESTS +set(CLI11_TESTS HelpersTest IniTest SimpleTest @@ -10,22 +10,23 @@ set(CLI_TESTS SubcommandTest HelpTest NewParseTest + OptionalTest ) -set(CLI_MULTIONLY_TESTS +set(CLI11_MULTIONLY_TESTS TimerTest ) # Only affects current directory, so safe include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -foreach(T ${CLI_TESTS}) +foreach(T ${CLI11_TESTS}) - add_executable(${T} ${T}.cpp ${CLI_headers}) + add_executable(${T} ${T}.cpp ${CLI11_headers}) target_link_libraries(${T} PUBLIC CLI11) add_gtest(${T}) - if(CLI_SINGLE_FILE AND CLI_SINGLE_FILE_TESTS) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) add_executable(${T}_Single ${T}.cpp) target_link_libraries(${T}_Single PUBLIC CLI11_SINGLE) add_gtest(${T}_Single) @@ -36,9 +37,9 @@ foreach(T ${CLI_TESTS}) endforeach() -foreach(T ${CLI_MULTIONLY_TESTS}) +foreach(T ${CLI11_MULTIONLY_TESTS}) - add_executable(${T} ${T}.cpp ${CLI_headers}) + add_executable(${T} ${T}.cpp ${CLI11_headers}) target_link_libraries(${T} PUBLIC CLI11) add_gtest(${T}) @@ -52,3 +53,32 @@ set_target_properties(link_test_1 PROPERTIES FOLDER "Tests") add_executable(link_test_2 link_test_2.cpp) target_link_libraries(link_test_2 PUBLIC CLI11 link_test_1) add_gtest(link_test_2) + +# Add informational printout +# Force this to be in a standard location so CTest can find it +add_executable(informational informational.cpp) +target_link_libraries(informational PUBLIC CLI11) +set_target_properties(informational PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}" + ) + +# Adding this printout to CTest +file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake" + "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")" + ) + +# Add boost to test boost::optional if available +find_package(Boost 1.35) +if(Boost_FOUND) + target_link_libraries(informational PUBLIC Boost::boost) + target_link_libraries(OptionalTest PUBLIC Boost::boost) + + # Enforce Boost::Optional even if __has_include is missing on your compiler + target_compile_definitions(informational PUBLIC CLI11_BOOST_OPTIONAL) + target_compile_definitions(OptionalTest PUBLIC CLI11_BOOST_OPTIONAL) +endif() + diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index b95e6c0a1..cee6d3bdd 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -125,7 +125,7 @@ TEST_F(TApp, IncorrectConstructionFlagPositional3) { TEST_F(TApp, IncorrectConstructionFlagExpected) { auto cat = app.add_flag("--cat"); - EXPECT_NO_THROW(cat->expected(0)); + EXPECT_THROW(cat->expected(0), CLI::IncorrectConstruction); EXPECT_THROW(cat->expected(1), CLI::IncorrectConstruction); } @@ -168,6 +168,13 @@ TEST_F(TApp, IncorrectConstructionNeedsCannotFind) { EXPECT_THROW(cat->needs("--nothing"), CLI::IncorrectConstruction); } +#ifndef CLI11_CPP20 +TEST_F(TApp, IncorrectConstructionRequiresCannotFind) { + auto cat = app.add_flag("--cat"); + EXPECT_THROW(cat->requires("--nothing"), CLI::IncorrectConstruction); +} +#endif + TEST_F(TApp, IncorrectConstructionExcludesCannotFind) { auto cat = app.add_flag("--cat"); EXPECT_THROW(cat->excludes("--nothing"), CLI::IncorrectConstruction); @@ -187,18 +194,20 @@ TEST_F(TApp, IncorrectConstructionDuplicateNeedsTxt) { EXPECT_THROW(cat->needs("--other"), CLI::OptionAlreadyAdded); } -TEST_F(TApp, IncorrectConstructionDuplicateExcludes) { +// Now allowed +TEST_F(TApp, CorrectConstructionDuplicateExcludes) { auto cat = app.add_flag("--cat"); auto other = app.add_flag("--other"); ASSERT_NO_THROW(cat->excludes(other)); - EXPECT_THROW(cat->excludes(other), CLI::OptionAlreadyAdded); + ASSERT_NO_THROW(other->excludes(cat)); } -TEST_F(TApp, IncorrectConstructionDuplicateExcludesTxt) { +// Now allowed +TEST_F(TApp, CorrectConstructionDuplicateExcludesTxt) { auto cat = app.add_flag("--cat"); - app.add_flag("--other"); + auto other = app.add_flag("--other"); ASSERT_NO_THROW(cat->excludes("--other")); - EXPECT_THROW(cat->excludes("--other"), CLI::OptionAlreadyAdded); + ASSERT_NO_THROW(other->excludes("--cat")); } TEST_F(TApp, CheckName) { diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index 60092d2d3..52b566afb 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -1,4 +1,4 @@ -#ifdef CLI_SINGLE_FILE +#ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else #include "CLI/CLI.hpp" @@ -203,6 +203,17 @@ TEST(THelp, ExcludesPositional) { EXPECT_THAT(help, HasSubstr("Excludes: op1")); } +TEST(THelp, ExcludesSymmetric) { + CLI::App app{"My prog"}; + + CLI::Option *op1 = app.add_flag("--op1"); + app.add_flag("--op2")->excludes(op1); + + std::string help = app.help(); + + EXPECT_THAT(help, HasSubstr("Excludes: --op2")); +} + TEST(THelp, ManualSetters) { CLI::App app{"My prog"}; diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index 0f6d87a60..281b8f6df 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -415,8 +415,8 @@ TEST(Types, LexicalCastParsable) { std::complex<double> output; EXPECT_TRUE(CLI::detail::lexical_cast(input, output)); - EXPECT_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble - EXPECT_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const + EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble + EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index 5c38870fc..a465618f4 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -34,8 +34,8 @@ TEST_F(TApp, AddingComplexParser) { run(); - EXPECT_EQ(1.5, comp.real()); - EXPECT_EQ(2.5, comp.imag()); + EXPECT_DOUBLE_EQ(1.5, comp.real()); + EXPECT_DOUBLE_EQ(2.5, comp.imag()); } TEST_F(TApp, DefaultComplex) { @@ -48,13 +48,13 @@ TEST_F(TApp, DefaultComplex) { EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); - EXPECT_EQ(1, comp.real()); - EXPECT_EQ(2, comp.imag()); + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); run(); - EXPECT_EQ(4, comp.real()); - EXPECT_EQ(3, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplex) { @@ -68,13 +68,13 @@ TEST_F(TApp, BuiltinComplex) { EXPECT_THAT(help, HasSubstr("2")); EXPECT_THAT(help, HasSubstr("COMPLEX")); - EXPECT_EQ(1, comp.real()); - EXPECT_EQ(2, comp.imag()); + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); run(); - EXPECT_EQ(4, comp.real()); - EXPECT_EQ(3, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexIgnoreI) { @@ -85,8 +85,8 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { run(); - EXPECT_EQ(4, comp.real()); - EXPECT_EQ(3, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexFail) { diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp new file mode 100644 index 000000000..305f42746 --- /dev/null +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -0,0 +1,31 @@ +#include <cstdlib> +#include <iostream> + +#include "app_helper.hpp" + +#ifdef CLI11_OPTIONAL + +TEST_F(TApp, OptionalTest) { + CLI::optional<int> opt; + app.add_option("-c,--count", opt); + run(); + EXPECT_FALSE(opt); + + app.reset(); + args = {"-c", "1"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, 1); + + app.reset(); + args = {"--count", "3"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, 3); +} + +#else + +TEST_F(TApp, DISABLED_OptionalTest) {} + +#endif diff --git a/packages/CLI11/tests/SimpleTest.cpp b/packages/CLI11/tests/SimpleTest.cpp index bc1f1ba0d..d87495f3e 100644 --- a/packages/CLI11/tests/SimpleTest.cpp +++ b/packages/CLI11/tests/SimpleTest.cpp @@ -1,4 +1,4 @@ -#ifdef CLI_SINGLE_FILE +#ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else #include "CLI/CLI.hpp" diff --git a/packages/CLI11/tests/app_helper.hpp b/packages/CLI11/tests/app_helper.hpp index 49ef7554a..4280cc9e8 100644 --- a/packages/CLI11/tests/app_helper.hpp +++ b/packages/CLI11/tests/app_helper.hpp @@ -1,6 +1,6 @@ #pragma once -#ifdef CLI_SINGLE_FILE +#ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else #include "CLI/CLI.hpp" diff --git a/packages/CLI11/tests/informational.cpp b/packages/CLI11/tests/informational.cpp new file mode 100644 index 000000000..89c73ff9a --- /dev/null +++ b/packages/CLI11/tests/informational.cpp @@ -0,0 +1,50 @@ +#ifdef CLI11_SINGLE_FILE +#include "CLI11.hpp" +#else +#include "CLI/CLI.hpp" +#endif + +#include <iostream> + +int main() { + std::cout << "\nCLI11 information:\n"; + + std::cout << " C++ standard: "; +#if defined(CLI11_CPP20) + std::cout << 20; +#elif defined(CLI11_CPP17) + std::cout << 17; +#elif defined(CLI11_CPP14) + std::cout << 14; +#else + std::cout << 11; +#endif + std::cout << "\n"; + + std::cout << " __has_include: "; +#ifdef __has_include + std::cout << "yes\n"; +#else + std::cout << "no\n"; +#endif + +#ifdef CLI11_OPTIONAL + std::cout << " [Available as CLI::optional]"; +#else + std::cout << " No optional library found\n"; +#endif + +#ifdef CLI11_STD_OPTIONAL + std::cout << " std::optional support active\n"; +#endif + +#ifdef CLI11_EXPERIMENTAL_OPTIONAL + std::cout << " std::experimental::optional support active\n"; +#endif + +#ifdef CLI11_BOOST_OPTIONAL + std::cout << " boost::optional support active\n"; +#endif + + std::cout << std::endl; +} -- GitLab