diff --git a/packages/CLI11/.appveyor.yml b/packages/CLI11/.appveyor.yml index 6d4f5b5e63fed94324d5e54520e07a672ac2fc60..91a95a011ef5e8e38e5433e3464abb9720a7987f 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 5b2d69bb61fc49989eecb993da8ffc4c55acc4af..0000000000000000000000000000000000000000 --- 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 5087f2be12e3d5c739ccec611fa59bac1de0732e..6772ecf97e98f4fbc4f84a51da18e8cc1d6cf724 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 3b89dd33c023ef192d82a326c522888a5a3acab0..8671d7881308cc3ac76908ff69764889c54430d8 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 55189d9e4624c340c15095cb5068047610b113b7..505b68eb9d47229b991e9c24a22d10b8ad6273c5 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 0000000000000000000000000000000000000000..2ba81aeeffa627ae010c61162012a7af75d7998b --- /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 e384b00347fff628d8ca88621d868d79d7fe02cf..0000000000000000000000000000000000000000 --- 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 cd7219a8ceb31d8bca77e7ab8a797e45a5798cb0..28d149a530571c3eb8d58dec4c2ac61284e504a9 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 49cc2f8ea8e2abfdcd4b214e2ea1bc328e115273..0000000000000000000000000000000000000000 --- 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 11c78bd365d1a7fbb3db8acc8765f341c4d4a563..8e84b44c4020d18140c540af9288b09a244d9df8 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 c02c44b065513bf4b5b9ff235f2870b1f7e938d8..872aa39f9a2743d04362ff254d9a04d146529473 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 279ebe327c47642b3f895c2570b3c74cb7f742f2..f1f52fe82d45b959d5c5bed7a9f0ae50df129ffe 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 f31547e3b96c63be237bd20fb5089125fad2c81c..040ae0e0033878a5e0d0b4575f5d74f9faf3b14d 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 f2c2cf0a11a0678a7934d0b8e6dc09ec9990d72e..437208ae94d859b7d832cc7912f49cec8ce86cf9 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 80eb9ed6e428ff6a41404f4265d52da89ccfe262..e6bf162ae678e462c141b6c21988f509b1120d88 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 77e584f9ad7b3da1070faa52746dcc3ba401b573..7a32f711da704bcd3ddb5d51e1842739fe2fc3bb 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 5363a26e1f607228ee15e48dcf8e7f87a8d4aada..0e2a8c75b89dfee49740639db146ac14a4837996 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 c2eef50cc20589d0c24004347da72be7fd1d3eea..8c2f13b3c048ea659b48abcac03f1c1df3011c48 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 827bee3925a0f7427d4e9dc4e824b19a6ec7b299..7018681faa76252585e2b249c810a6d8facba606 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 8fd85e4709e40cf421b7f8a56c3a6c84b4637796..543b85a19dd3c32477d326c1404f7f73eec6cf6a 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 53a0cef230346bb583355c02d4c8b0de4ee077d4..662e833dd47d32f029bd564684fb689250383d24 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 bd47806b038e7992bacfc2abc5739e43b0309f2f..09104a651f5ef17ffeb5e2f05f6dc0924aaacfa1 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 0000000000000000000000000000000000000000..9c10b0814aa82360a3477bdec9b0a8dc3b76d598 --- /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 f8222d3491ccad85b901524b1b155e0197c85714..dd61dfd9dad08e129d64201521d82db468787b05 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 0000000000000000000000000000000000000000..1597da336a45a3f9944dd5d8ee5f0a50f20b79de --- /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 fecdbafd29d6214b54c71495ec144c3df44ea20c..aea4b8568796e94a690a16f7d979357167a17cf1 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 e6985beb1d8ff10752a5fb913945f0958ba45c9f..d260ab959024a04c52871a0bc3ae6c9b44b143f5 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 e53f9a13530a251a72f9a36973c5de306c83fcf9..0b1c241647e431c0b84390922832552bc5904780 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 e2ae6d2c85e9c4badac2f8e2c85b5defd59b8f90..4c191fcc4e4a7e518f2a1539e49142e77be1cca2 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 b95e6c0a167706093c3216f02f0aa87d5683cdf3..cee6d3bddfaa18f64d9b41ed35f8b83cf28d2402 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 60092d2d35c0c20b757d1d9158e7ec5974a7ab4c..52b566afbb6eefb2105f768aa0369e4010968e81 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 0f6d87a6090d2cea5eda013dc6e6f6bdeb5aa9e5..281b8f6df2ffecd5e20d88df7f6fe0b1bc0e4b15 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 5c38870fc7d650cbe865fb6a19cb4c15922814a5..a465618f46b525f5851997d57fa460567b8b57cc 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 0000000000000000000000000000000000000000..305f42746ae4c900a86e6274782b6190f75f6f4f --- /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 bc1f1ba0db655f6b2f25226ac05dab07c76193d1..d87495f3e5e91fa7eeaea291292129e266976a71 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 49ef7554abf38184549ea7ff984e365a52633702..4280cc9e802277a6bd18652b07d675a5c5b22652 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 0000000000000000000000000000000000000000..89c73ff9aa3040e59a1c9735719c526edaf441ba --- /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; +}