diff --git a/modules/cxxopts/.github/workflows/build.yml b/modules/cxxopts/.github/workflows/build.yml new file mode 100644 index 0000000..3d35680 --- /dev/null +++ b/modules/cxxopts/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Build cxxopts + +on: + push: + branches: [ $default-branch ] + pull_request: + branches: [ $default-branch ] + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: setup + run: mkdir build; cd build + - name: cmake + run: cmake .. + - name: Build + run: make -j$(nproc) + - name: test + run: ctest diff --git a/modules/cxxopts/.github/workflows/cmake.yml b/modules/cxxopts/.github/workflows/cmake.yml new file mode 100644 index 0000000..3589fcb --- /dev/null +++ b/modules/cxxopts/.github/workflows/cmake.yml @@ -0,0 +1,44 @@ +name: CMake + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ${{matrix.os}} + + strategy: + matrix: + os: [ubuntu-18.04] + compiler: [g++-7, g++-9, g++-10, clang++] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=${{matrix.compiler}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + diff --git a/modules/cxxopts/.gitignore b/modules/cxxopts/.gitignore index e91cd40..4038fb1 100644 --- a/modules/cxxopts/.gitignore +++ b/modules/cxxopts/.gitignore @@ -1,8 +1,9 @@ *.swp -build* +build*/ CMakeCache.txt Makefile CMakeFiles/ Testing/ CTestTestfile.cmake cmake_install.cmake +bazel-* diff --git a/modules/cxxopts/.travis.yml b/modules/cxxopts/.travis.yml index 87c78a7..942f03f 100644 --- a/modules/cxxopts/.travis.yml +++ b/modules/cxxopts/.travis.yml @@ -6,21 +6,35 @@ os: matrix: include: - os: linux - env: COMPILER=g++-4.9 + env: COMPILER=g++-6 addons: apt: packages: - - g++-4.9 + - g++-6 sources: &sources - llvm-toolchain-trusty-3.8 - llvm-toolchain-trusty-5.0 - ubuntu-toolchain-r-test - os: linux - env: COMPILER=g++-4.9 UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes + env: COMPILER=g++-6 UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes addons: apt: packages: - - g++-4.9 + - g++-6 + sources: *sources + - os: linux + env: COMPILER=g++-7 + addons: + apt: + packages: + - g++-7 + sources: *sources + - os: linux + env: COMPILER=g++-7 UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes + addons: + apt: + packages: + - g++-7 sources: *sources - os: linux env: COMPILER=g++-5 @@ -36,6 +50,20 @@ matrix: packages: - g++-5 sources: *sources + - os: linux + env: COMPILER=g++-4.8 + addons: + apt: + packages: + - g++-4.8 + sources: *sources + - os: linux + env: COMPILER=g++-4.8 UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes + addons: + apt: + packages: + - g++-4.8 + sources: *sources - os: linux env: COMPILER=clang++-3.8 CXXFLAGS=-stdlib=libc++ addons: @@ -61,7 +89,7 @@ matrix: - g++-5 sources: *sources script: > - cmake -DCXXOPTS_BUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$COMPILER + cmake -Werror=dev -DCXXOPTS_BUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_CXX_FLAGS=$CXXFLAGS $UNICODE_OPTIONS $CMAKE_OPTIONS . && make && make ARGS=--output-on-failure test diff --git a/modules/cxxopts/BUILD.bazel b/modules/cxxopts/BUILD.bazel new file mode 100644 index 0000000..a66fae7 --- /dev/null +++ b/modules/cxxopts/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "cxxopts", + hdrs = ["include/cxxopts.hpp"], + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) diff --git a/modules/cxxopts/CHANGELOG.md b/modules/cxxopts/CHANGELOG.md index 4bddcff..c50fae0 100644 --- a/modules/cxxopts/CHANGELOG.md +++ b/modules/cxxopts/CHANGELOG.md @@ -3,7 +3,7 @@ This is the changelog for `cxxopts`, a C++11 library for parsing command line options. The project adheres to semantic versioning. -## Next version +## 3.0 ### Changed @@ -12,6 +12,22 @@ options. The project adheres to semantic versioning. * Fix duplicate default options when there is a short and long option. * Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions. * Fix char parsing for space and check for length. +* Change argument type in `Options::parse` from `char**` to `const char**`. +* Refactor parser to not change its arguments. +* `ParseResult` doesn't depend on a reference to the parser. +* Fixed several warnings and code quality issues. +* Improved formatting for help descriptions. +* Improve integer parsing. + +### Added + +* A list of unmatched arguments is available in `ParseResult`. +* Support single letter options with argument attached. +* Use if it is present. + +### Bug Fixes + +* Fix missing option name in exception. ## 2.2 diff --git a/modules/cxxopts/CMakeLists.txt b/modules/cxxopts/CMakeLists.txt index 1b524f7..4c1a686 100644 --- a/modules/cxxopts/CMakeLists.txt +++ b/modules/cxxopts/CMakeLists.txt @@ -1,15 +1,15 @@ # Copyright (c) 2014 Jarryd Beck -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -17,92 +17,66 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1...3.19) -# parse the current version from the cxxopts header -file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/cxxopts.hpp" cxxopts_version_defines - REGEX "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH)") -foreach(ver ${cxxopts_version_defines}) - if(ver MATCHES "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") - set(CXXOPTS__VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") - endif() -endforeach() -set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH}) -message(STATUS "cxxopts version ${VERSION}") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") +include(cxxopts) +set("PROJECT_DESCRIPTION" "A header-only lightweight C++ command line option parser") +set("PROJECT_HOMEPAGE_URL" "https://github.com/jarro2783/cxxopts") -project(cxxopts VERSION "${VERSION}" LANGUAGES CXX) +# Get the version of the library +cxxopts_getversion(VERSION) -enable_testing() +project(cxxopts + VERSION "${VERSION}" + LANGUAGES CXX +) -option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ON) -option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ON) -option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ON) +# Must include after the project call due to GNUInstallDirs requiring a language be enabled (IE. CXX) +include(GNUInstallDirs) -# request c++11 without gnu extension for the whole project and enable more warnings -if (CXXOPTS_CXX_STANDARD) - set(CMAKE_CXX_STANDARD ${CXXOPTS_CXX_STANDARD}) -else() - set(CMAKE_CXX_STANDARD 11) +# Determine whether this is a standalone project or included by other projects +set(CXXOPTS_STANDALONE_PROJECT OFF) +if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CXXOPTS_STANDALONE_PROJECT ON) endif() -set(CMAKE_CXX_EXTENSIONS OFF) +# Establish the project options +option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_ENABLE_WARNINGS "Add warnings to CMAKE_CXX_FLAGS" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_USE_UNICODE_HELP "Use ICU Unicode library" OFF) -if(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W2") -elseif(CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -Wshadow") +if (CXXOPTS_STANDALONE_PROJECT) + cxxopts_set_cxx_standard() +endif() + +if (CXXOPTS_ENABLE_WARNINGS) + cxxopts_enable_warnings() endif() add_library(cxxopts INTERFACE) add_library(cxxopts::cxxopts ALIAS cxxopts) +add_subdirectory(include) -# optionally, enable unicode support using the ICU library -set(CXXOPTS_USE_UNICODE_HELP FALSE CACHE BOOL "Use ICU Unicode library") +# Link against the ICU library when requested if(CXXOPTS_USE_UNICODE_HELP) - find_package(PkgConfig) - pkg_check_modules(ICU REQUIRED icu-uc) - - target_link_libraries(cxxopts INTERFACE ${ICU_LDFLAGS}) - target_compile_options(cxxopts INTERFACE ${ICU_CFLAGS}) - target_compile_definitions(cxxopts INTERFACE CXXOPTS_USE_UNICODE) + cxxopts_use_unicode() endif() -target_include_directories(cxxopts INTERFACE - $ - $ - ) - -if(CXXOPTS_ENABLE_INSTALL) - include(CMakePackageConfigHelpers) - set(CXXOPTS_CMAKE_DIR "lib/cmake/cxxopts" CACHE STRING - "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") - set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") - set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") - set(targets_export_name cxxopts-targets) - - # Generate the version, config and target files into the build directory. - write_basic_package_version_file( - ${version_config} - VERSION ${VERSION} - COMPATIBILITY AnyNewerVersion) - configure_package_config_file( - ${PROJECT_SOURCE_DIR}/cxxopts-config.cmake.in - ${project_config} - INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR}) - export(TARGETS cxxopts NAMESPACE cxxopts:: - FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) - - # Install version, config and target files. - install( - FILES ${project_config} ${version_config} - DESTINATION ${CXXOPTS_CMAKE_DIR}) - install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR} - NAMESPACE cxxopts::) +# Install cxxopts when requested by the user +if (CXXOPTS_ENABLE_INSTALL) + cxxopts_install_logic() +endif() - # Install the header file and export the target - install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION lib) - install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION include) +# Build examples when requested by the user +if (CXXOPTS_BUILD_EXAMPLES) + add_subdirectory(src) endif() -add_subdirectory(src) -add_subdirectory(test) +# Enable testing when requested by the user +if (CXXOPTS_BUILD_TESTS) + enable_testing() + add_subdirectory(test) +endif() diff --git a/modules/cxxopts/README.md b/modules/cxxopts/README.md index 88b92d4..ede30ef 100644 --- a/modules/cxxopts/README.md +++ b/modules/cxxopts/README.md @@ -5,6 +5,25 @@ Note that `master` is generally a work in progress, and you probably want to use a tagged release version. +## Version 3 breaking changes + +If you have used version 2, there are a couple of breaking changes in version 3 +that you should be aware of. If you are new to `cxxopts` you can skip this +section. + +The parser no longer modifies its arguments, so you can pass a const `argc` and +`argv` and expect them not to be changed. + +The `ParseResult` object no longer depends on the parser. So it can be returned +from a scope outside the parser and still work. Now that the inputs are not +modified, `ParseResult` stores a list of the unmatched arguments. These are +retrieved like follows: + +```cpp +auto result = options.parse(argc, argv); +result.unmatched(); // get the unmatched arguments +``` + # Quick start This is a lightweight C++ option parser library, supporting the standard GNU @@ -69,6 +88,23 @@ exception will be thrown. Note that the result of `options.parse` should only be used as long as the `options` object that created it is in scope. +## Unrecognised arguments + +You can allow unrecognised arguments to be skipped. This applies to both +positional arguments that are not parsed into another option, and `--` +arguments that do not match an argument that you specify. This is done by +calling: + +```cpp +options.allow_unrecognised_options(); +``` + +and in the result object they are retrieved with: + +```cpp +result.unmatched() +``` + ## Exceptions Exceptional situations throw C++ exceptions. There are two types of @@ -125,6 +161,8 @@ Note that the default and implicit value is always stored as a string, regardless of the type that you want to store it in. It will be parsed as though it was given on the command line. +Default values are not counted by `Options::count`. + ## Boolean values Boolean options have a default implicit value of `"true"`, which can be diff --git a/modules/cxxopts/WORKSPACE b/modules/cxxopts/WORKSPACE new file mode 100644 index 0000000..e69de29 diff --git a/modules/cxxopts/cmake/cxxopts.cmake b/modules/cxxopts/cmake/cxxopts.cmake new file mode 100644 index 0000000..46e87ba --- /dev/null +++ b/modules/cxxopts/cmake/cxxopts.cmake @@ -0,0 +1,158 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +if (CMAKE_VERSION VERSION_GREATER 3.10 OR CMAKE_VERSION VERSION_EQUAL 3.10) + # Use include_guard() added in cmake 3.10 + include_guard() +endif() + +include(CMakePackageConfigHelpers) + +function(cxxopts_getversion version_arg) + # Parse the current version from the cxxopts header + file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/cxxopts.hpp" cxxopts_version_defines + REGEX "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH)") + foreach(ver ${cxxopts_version_defines}) + if(ver MATCHES "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") + set(CXXOPTS__VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") + endif() + endforeach() + set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH}) + + # Give feedback to the user. Prefer DEBUG when available since large projects tend to have a lot + # going on already + if (CMAKE_VERSION VERSION_GREATER 3.15 OR CMAKE_VERSION VERSION_EQUAL 3.15) + message(DEBUG "cxxopts version ${VERSION}") + else() + message(STATUS "cxxopts version ${VERSION}") + endif() + + # Return the information to the caller + set(${version_arg} ${VERSION} PARENT_SCOPE) +endfunction() + +# Optionally, enable unicode support using the ICU library +function(cxxopts_use_unicode) + find_package(PkgConfig) + pkg_check_modules(ICU REQUIRED icu-uc) + + target_link_libraries(cxxopts INTERFACE ${ICU_LDFLAGS}) + target_compile_options(cxxopts INTERFACE ${ICU_CFLAGS}) + target_compile_definitions(cxxopts INTERFACE CXXOPTS_USE_UNICODE) +endfunction() + +# Request C++11 without gnu extension for the whole project and enable more warnings +macro(cxxopts_set_cxx_standard) + if (CXXOPTS_CXX_STANDARD) + set(CMAKE_CXX_STANDARD ${CXXOPTS_CXX_STANDARD}) + else() + set(CMAKE_CXX_STANDARD 11) + endif() + + set(CMAKE_CXX_EXTENSIONS OFF) +endmacro() + +# Helper function to enable warnings +function(cxxopts_enable_warnings) + if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W2") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER_EQUAL 5.0) + set(COMPILER_SPECIFIC_FLAGS "-Wsuggest-override") + endif() + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -Wshadow -Weffc++ -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion ${COMPILER_SPECIFIC_FLAGS}") + endif() + + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE) +endfunction() + +# Helper function to ecapsulate install logic +function(cxxopts_install_logic) + string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + set(CXXOPTS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/cxxopts" CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") + set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") + set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") + set(targets_export_name cxxopts-targets) + set(PackagingTemplatesDir "${PROJECT_SOURCE_DIR}/packaging") + + + if(${CMAKE_VERSION} VERSION_GREATER "3.14") + set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") + endif() + + # Generate the version, config and target files into the build directory. + write_basic_package_version_file( + ${version_config} + VERSION ${VERSION} + COMPATIBILITY AnyNewerVersion + ${OPTIONAL_ARCH_INDEPENDENT} + ) + configure_package_config_file( + ${PackagingTemplatesDir}/cxxopts-config.cmake.in + ${project_config} + INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR}) + export(TARGETS cxxopts NAMESPACE cxxopts:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + + # Install version, config and target files. + install( + FILES ${project_config} ${version_config} + DESTINATION ${CXXOPTS_CMAKE_DIR}) + install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR} + NAMESPACE cxxopts::) + + # Install the header file and export the target + install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + set(CPACK_PACKAGE_VENDOR "cxxopt developers") + set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") + set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") + set(CPACK_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + + set(CPACK_DEBIAN_PACKAGE_NAME "lib${PROJECT_NAME}-dev") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6-dev") + set(CPACK_DEBIAN_PACKAGE_SUGGESTS "cmake, pkg-config, pkg-conf") + + set(CPACK_RPM_PACKAGE_NAME "lib${PROJECT_NAME}-devel") + set(CPACK_RPM_PACKAGE_SUGGESTS "${CPACK_DEBIAN_PACKAGE_SUGGESTS}") + + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_RPM_COMPONENT_INSTALL ON) + set(CPACK_NSIS_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") + + set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc") + configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY) + install(FILES "${PKG_CONFIG_FILE_NAME}" + DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/pkgconfig" + ) + + include(CPack) +endfunction() diff --git a/modules/cxxopts/include/CMakeLists.txt b/modules/cxxopts/include/CMakeLists.txt new file mode 100644 index 0000000..1123c5a --- /dev/null +++ b/modules/cxxopts/include/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +target_include_directories(cxxopts INTERFACE + $ + $ +) diff --git a/modules/cxxopts/include/cxxopts.hpp b/modules/cxxopts/include/cxxopts.hpp index ae95343..12456a7 100644 --- a/modules/cxxopts/include/cxxopts.hpp +++ b/modules/cxxopts/include/cxxopts.hpp @@ -30,19 +30,35 @@ THE SOFTWARE. #include #include #include +#include #include #include -#include #include #include #include #include #include #include +#include -#ifdef __cpp_lib_optional -#include -#define CXXOPTS_HAS_OPTIONAL +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif #endif #if __cplusplus >= 201603L @@ -55,10 +71,14 @@ THE SOFTWARE. #define CXXOPTS_VECTOR_DELIMITER ',' #endif -#define CXXOPTS__VERSION_MAJOR 2 -#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 0 #define CXXOPTS__VERSION_PATCH 0 +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + namespace cxxopts { static constexpr struct { @@ -81,7 +101,7 @@ namespace cxxopts namespace cxxopts { - typedef icu::UnicodeString String; + using String = icu::UnicodeString; inline String @@ -90,6 +110,14 @@ namespace cxxopts return icu::UnicodeString::fromUTF8(std::move(s)); } +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif class UnicodeStringIterator : public std::iterator { @@ -136,6 +164,9 @@ namespace cxxopts const icu::UnicodeString* s; int32_t i; }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif inline String& @@ -146,9 +177,9 @@ namespace cxxopts inline String& - stringAppend(String& s, int n, UChar32 c) + stringAppend(String& s, size_t n, UChar32 c) { - for (int i = 0; i != n; ++i) + for (size_t i = 0; i != n; ++i) { s.append(c); } @@ -216,7 +247,7 @@ namespace std namespace cxxopts { - typedef std::string String; + using String = std::string; template T @@ -284,6 +315,14 @@ namespace cxxopts #endif } // namespace +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif class Value : public std::enable_shared_from_this { public: @@ -327,7 +366,9 @@ namespace cxxopts virtual bool is_boolean() const = 0; }; - +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif class OptionException : public std::exception { public: @@ -455,7 +496,7 @@ namespace cxxopts public: explicit option_has_no_value_exception(const std::string& option) : OptionException( - option.empty() ? + !option.empty() ? ("Option " + LQUOTE + option + RQUOTE + " has no value") : "Option has no value") { @@ -508,15 +549,257 @@ namespace cxxopts namespace values { - namespace + namespace parser_tool { - std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); - std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); - } // namespace + struct IntegerDesc + { + std::string negative = ""; + std::string base = ""; + std::string value = ""; + }; + struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; + }; +#ifdef CXXOPTS_NO_REGEX + inline IntegerDesc SplitInteger(const std::string &text) + { + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; + } + + inline bool IsTrueText(const std::string &text) + { + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; + } + + inline bool IsFalseText(const std::string &text) + { + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; + } + + inline std::pair SplitSwitchDef(const std::string &text) + { + std::string short_sw, long_sw; + const char *pdata = text.c_str(); + if (isalnum(*pdata) && *(pdata + 1) == ',') { + short_sw = std::string(1, *pdata); + pdata += 2; + } + while (*pdata == ' ') { pdata += 1; } + if (isalnum(*pdata)) { + const char *store = pdata; + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { + pdata += 1; + } + if (*pdata == '\0') { + long_sw = std::string(store, pdata - store); + } else { + throw_or_mimic(text); + } + } + return std::pair(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; + } + +#else // CXXOPTS_NO_REGEX + + namespace + { + + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + } // namespace + + inline IntegerDesc SplitInteger(const std::string &text) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; + } + + inline bool IsTrueText(const std::string &text) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + return !result.empty(); + } + + inline bool IsFalseText(const std::string &text) + { + std::smatch result; + std::regex_match(text, result, falsy_pattern); + return !result.empty(); + } + + inline std::pair SplitSwitchDef(const std::string &text) + { + std::match_results result; + std::regex_match(text.c_str(), result, option_specifier); + if (result.empty()) + { + throw_or_mimic(text); + } + + const std::string& short_sw = result[2]; + const std::string& long_sw = result[3]; + + return std::pair(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; + } + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX + } namespace detail { @@ -552,7 +835,7 @@ namespace cxxopts { template void - operator()(bool, U, const std::string&) {} + operator()(bool, U, const std::string&) const {} }; template @@ -564,66 +847,52 @@ namespace cxxopts } // namespace detail template - R - checked_negate(T&& t, const std::string&, std::true_type) + void + checked_negate(R& r, T&& t, const std::string&, std::true_type) { // if we got to here, then `t` is a positive number that fits into // `R`. So to avoid MSVC C4146, we first cast it to `R`. // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - return static_cast(-static_cast(t-1)-1); + r = static_cast(-static_cast(t-1)-1); } template - T - checked_negate(T&& t, const std::string& text, std::false_type) + void + checked_negate(R&, T&&, const std::string& text, std::false_type) { throw_or_mimic(text); - return t; } template void integer_parser(const std::string& text, T& value) { - std::smatch match; - std::regex_match(text, match, integer_pattern); - - if (match.length() == 0) - { - throw_or_mimic(text); - } - - if (match.length(4) > 0) - { - value = 0; - return; - } + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); using US = typename std::make_unsigned::type; - constexpr bool is_signed = std::numeric_limits::is_signed; - const bool negative = match.length(1) > 0; - const uint8_t base = match.length(2) > 0 ? 16 : 10; - auto value_match = match[3]; + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; US result = 0; - for (auto iter = value_match.first; iter != value_match.second; ++iter) + for (char ch : value_match) { US digit = 0; - if (*iter >= '0' && *iter <= '9') + if (ch >= '0' && ch <= '9') { - digit = static_cast(*iter - '0'); + digit = static_cast(ch - '0'); } - else if (base == 16 && *iter >= 'a' && *iter <= 'f') + else if (base == 16 && ch >= 'a' && ch <= 'f') { - digit = static_cast(*iter - 'a' + 10); + digit = static_cast(ch - 'a' + 10); } - else if (base == 16 && *iter >= 'A' && *iter <= 'F') + else if (base == 16 && ch >= 'A' && ch <= 'F') { - digit = static_cast(*iter - 'A' + 10); + digit = static_cast(ch - 'A' + 10); } else { @@ -643,9 +912,7 @@ namespace cxxopts if (negative) { - value = checked_negate(result, - text, - std::integral_constant()); + checked_negate(value, result, text, std::integral_constant()); } else { @@ -663,77 +930,25 @@ namespace cxxopts } } - inline - void - parse_value(const std::string& text, uint8_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int8_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, uint16_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int16_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, uint32_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int32_t& value) + template ::value>::type* = nullptr + > + void parse_value(const std::string& text, T& value) { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, uint64_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int64_t& value) - { - integer_parser(text, value); + integer_parser(text, value); } inline void parse_value(const std::string& text, bool& value) { - std::smatch result; - std::regex_match(text, result, truthy_pattern); - - if (!result.empty()) + if (parser_tool::IsTrueText(text)) { value = true; return; } - std::regex_match(text, result, falsy_pattern); - if (!result.empty()) + if (parser_tool::IsFalseText(text)) { value = false; return; @@ -752,7 +967,9 @@ namespace cxxopts // The fallback parser. It uses the stringstream parser to parse all types // that have not been overloaded explicitly. It has to be placed in the // source code before all other more specialized templates. - template + template ::value>::type* = nullptr + > void parse_value(const std::string& text, T& value) { stringstream_parser(text, value); @@ -762,6 +979,12 @@ namespace cxxopts void parse_value(const std::string& text, std::vector& value) { + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } std::stringstream in(text); std::string token; while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { @@ -824,6 +1047,8 @@ namespace cxxopts ~abstract_value() override = default; + abstract_value& operator=(const abstract_value&) = default; + abstract_value(const abstract_value& rhs) { if (rhs.m_result) @@ -924,14 +1149,14 @@ namespace cxxopts } protected: - std::shared_ptr m_result; - T* m_store; + std::shared_ptr m_result{}; + T* m_store{}; bool m_default = false; bool m_implicit = false; - std::string m_default_value; - std::string m_implicit_value; + std::string m_default_value{}; + std::string m_implicit_value{}; }; template @@ -942,7 +1167,7 @@ namespace cxxopts CXXOPTS_NODISCARD std::shared_ptr - clone() const + clone() const override { return std::make_shared>(*this); } @@ -1016,13 +1241,14 @@ namespace cxxopts , m_value(std::move(val)) , m_count(0) { + m_hash = std::hash{}(m_long + m_short); } OptionDetails(const OptionDetails& rhs) : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) , m_count(rhs.m_count) { - m_value = rhs.m_value->clone(); } OptionDetails(OptionDetails&& rhs) = default; @@ -1061,12 +1287,20 @@ namespace cxxopts return m_long; } + size_t + hash() const + { + return m_hash; + } + private: - std::string m_short; - std::string m_long; - String m_desc; - std::shared_ptr m_value; + std::string m_short{}; + std::string m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; int m_count; + + size_t m_hash{}; }; struct HelpOptionDetails @@ -1085,9 +1319,9 @@ namespace cxxopts struct HelpGroupDetails { - std::string name; - std::string description; - std::vector options; + std::string name{}; + std::string description{}; + std::vector options{}; }; class OptionValue @@ -1115,6 +1349,17 @@ namespace cxxopts m_value->parse(); } + void + parse_no_value(const std::shared_ptr& details) + { + m_long_name = &details->long_name(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + CXXOPTS_NODISCARD size_t count() const noexcept @@ -1122,6 +1367,10 @@ namespace cxxopts return m_count; } +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic pop +#endif + // TODO: maybe default options should count towards the number of arguments CXXOPTS_NODISCARD bool @@ -1156,10 +1405,11 @@ namespace cxxopts } } + const std::string* m_long_name = nullptr; - // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, - // where the key has the string we point to. - std::shared_ptr m_value; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; size_t m_count = 0; bool m_default = false; }; @@ -1201,45 +1451,64 @@ namespace cxxopts std::string m_value; }; + using ParsedHashMap = std::unordered_map; + using NameHashMap = std::unordered_map; + class ParseResult { public: - ParseResult( - std::shared_ptr< - std::unordered_map> - >, - std::vector, - bool allow_unrecognised, - int&, char**&); + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; size_t count(const std::string& o) const { - auto iter = m_options->find(o); - if (iter == m_options->end()) + auto iter = m_keys.find(o); + if (iter == m_keys.end()) { return 0; } - auto riter = m_results.find(iter->second); + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } - return riter->second.count(); + return viter->second.count(); } const OptionValue& operator[](const std::string& option) const { - auto iter = m_options->find(option); + auto iter = m_keys.find(option); - if (iter == m_options->end()) + if (iter == m_keys.end()) { throw_or_mimic(option); } - auto riter = m_results.find(iter->second); + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } - return riter->second; + return viter->second; } const std::vector& @@ -1248,49 +1517,17 @@ namespace cxxopts return m_sequential; } - private: - - void - parse(int& argc, char**& argv); - - void - add_to_option(const std::string& option, const std::string& arg); - - bool - consume_positional(const std::string& a); - - void - parse_option - ( - const std::shared_ptr& value, - const std::string& name, - const std::string& arg = "" - ); - - void - parse_default(const std::shared_ptr& details); - - void - checked_parse_arg - ( - int argc, - char* argv[], - int& current, - const std::shared_ptr& value, - const std::string& name - ); - - const std::shared_ptr< - std::unordered_map> - > m_options; - std::vector m_positional; - std::vector::iterator m_next_positional; - std::unordered_set m_positional_set; - std::unordered_map, OptionValue> m_results; - - bool m_allow_unrecognised; + const std::vector& + unmatched() const + { + return m_unmatched; + } - std::vector m_sequential; + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_unmatched{}; }; struct Option @@ -1315,9 +1552,69 @@ namespace cxxopts std::string arg_help_; }; + using OptionMap = std::unordered_map>; + using PositionalList = std::vector; + using PositionalListIterator = PositionalList::const_iterator; + + class OptionParser + { + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; + }; + class Options { - using OptionMap = std::unordered_map>; public: explicit Options(std::string program, std::string help_string = "") @@ -1327,8 +1624,9 @@ namespace cxxopts , m_positional_help("positional parameters") , m_show_positional(false) , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) , m_options(std::make_shared()) - , m_next_positional(m_positional.end()) { } @@ -1360,8 +1658,22 @@ namespace cxxopts return *this; } + Options& + set_width(size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + ParseResult - parse(int& argc, char**& argv); + parse(int argc, const char* const* argv); OptionAdder add_options(std::string group = ""); @@ -1438,20 +1750,24 @@ namespace cxxopts void generate_all_groups_help(String& result) const; - std::string m_program; - String m_help_string; - std::string m_custom_help; - std::string m_positional_help; + std::string m_program{}; + String m_help_string{}; + std::string m_custom_help{}; + std::string m_positional_help{}; bool m_show_positional; bool m_allow_unrecognised; + size_t m_width; + bool m_tab_expansion; std::shared_ptr m_options; - std::vector m_positional; - std::vector::iterator m_next_positional; - std::unordered_set m_positional_set; + std::vector m_positional{}; + std::unordered_set m_positional_set{}; //mapping from groups to help options - std::map m_help; + std::map m_help{}; + + std::list m_option_list{}; + std::unordered_map m_option_map{}; }; class OptionAdder @@ -1480,14 +1796,8 @@ namespace cxxopts namespace { - constexpr int OPTION_LONGEST = 30; - constexpr int OPTION_DESC_GAP = 2; - - std::basic_regex option_matcher - ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); - - std::basic_regex option_specifier - ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + constexpr size_t OPTION_LONGEST = 30; + constexpr size_t OPTION_DESC_GAP = 2; String format_option @@ -1540,7 +1850,8 @@ namespace cxxopts ( const HelpOptionDetails& o, size_t start, - size_t width + size_t allowed, + bool tab_expansion ) { auto desc = o.desc; @@ -1559,77 +1870,112 @@ namespace cxxopts String result; + if (tab_expansion) + { + String desc2; + auto size = size_t{ 0 }; + for (auto c = std::begin(desc); c != std::end(desc); ++c) + { + if (*c == '\n') + { + desc2 += *c; + size = 0; + } + else if (*c == '\t') + { + auto skip = 8 - size % 8; + stringAppend(desc2, skip, ' '); + size += skip; + } + else + { + desc2 += *c; + ++size; + } + } + desc = desc2; + } + + desc += " "; + auto current = std::begin(desc); + auto previous = current; auto startLine = current; auto lastSpace = current; auto size = size_t{}; + bool appendNewLine; + bool onlyWhiteSpace = true; + while (current != std::end(desc)) { - if (*current == ' ') + appendNewLine = false; + + if (std::isblank(*previous)) { lastSpace = current; } - if (*current == '\n') + if (!std::isblank(*current)) + { + onlyWhiteSpace = false; + } + + while (*current == '\n') { - startLine = current + 1; - lastSpace = startLine; + previous = current; + ++current; + appendNewLine = true; } - else if (size > width) + + if (!appendNewLine && size >= allowed) { - if (lastSpace == startLine) + if (lastSpace != startLine) { - stringAppend(result, startLine, current + 1); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = current + 1; - lastSpace = startLine; + current = lastSpace; + previous = current; } - else + appendNewLine = true; + } + + if (appendNewLine) + { + stringAppend(result, startLine, current); + startLine = current; + lastSpace = current; + + if (*previous != '\n') { - stringAppend(result, startLine, lastSpace); stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = lastSpace + 1; - lastSpace = startLine; } + + stringAppend(result, start, ' '); + + if (*previous != '\n') + { + stringAppend(result, lastSpace, current); + } + + onlyWhiteSpace = true; size = 0; } - else - { - ++size; - } + previous = current; ++current; + ++size; } - //append whatever is left - stringAppend(result, startLine, current); + //append whatever is left but ignore whitespace + if (!onlyWhiteSpace) + { + stringAppend(result, startLine, previous); + } return result; } } // namespace -inline -ParseResult::ParseResult -( - std::shared_ptr< - std::unordered_map> - > options, - std::vector positional, - bool allow_unrecognised, - int& argc, char**& argv -) -: m_options(std::move(options)) -, m_positional(std::move(positional)) -, m_next_positional(m_positional.begin()) -, m_allow_unrecognised(allow_unrecognised) -{ - parse(argc, argv); -} - inline void Options::add_options @@ -1662,37 +2008,30 @@ OptionAdder::operator() std::string arg_help ) { - std::match_results result; - std::regex_match(opts.c_str(), result, option_specifier); + std::string short_sw, long_sw; + std::tie(short_sw, long_sw) = values::parser_tool::SplitSwitchDef(opts); - if (result.empty()) + if (!short_sw.length() && !long_sw.length()) { throw_or_mimic(opts); } - - const auto& short_match = result[2]; - const auto& long_match = result[3]; - - if (!short_match.length() && !long_match.length()) - { - throw_or_mimic(opts); - } else if (long_match.length() == 1 && short_match.length()) + else if (long_sw.length() == 1 && short_sw.length()) { throw_or_mimic(opts); } auto option_names = [] ( - const std::sub_match& short_, - const std::sub_match& long_ + const std::string &short_, + const std::string &long_ ) { if (long_.length() == 1) { - return std::make_tuple(long_.str(), short_.str()); + return std::make_tuple(long_, short_); } - return std::make_tuple(short_.str(), long_.str()); - }(short_match, long_match); + return std::make_tuple(short_, long_); + }(short_sw, long_sw); m_options.add_option ( @@ -1709,21 +2048,32 @@ OptionAdder::operator() inline void -ParseResult::parse_default(const std::shared_ptr& details) +OptionParser::parse_default(const std::shared_ptr& details) +{ + // TODO: remove the duplicate code here + auto& store = m_parsed[details->hash()]; + store.parse_default(details); +} + +inline +void +OptionParser::parse_no_value(const std::shared_ptr& details) { - m_results[details].parse_default(details); + auto& store = m_parsed[details->hash()]; + store.parse_no_value(details); } inline void -ParseResult::parse_option +OptionParser::parse_option ( const std::shared_ptr& value, const std::string& /*name*/, const std::string& arg ) { - auto& result = m_results[value]; + auto hash = value->hash(); + auto& result = m_parsed[hash]; result.parse(value, arg); m_sequential.emplace_back(value->long_name(), arg); @@ -1731,10 +2081,10 @@ ParseResult::parse_option inline void -ParseResult::checked_parse_arg +OptionParser::checked_parse_arg ( int argc, - char* argv[], + const char* const* argv, int& current, const std::shared_ptr& value, const std::string& name @@ -1767,43 +2117,36 @@ ParseResult::checked_parse_arg inline void -ParseResult::add_to_option(const std::string& option, const std::string& arg) +OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg) { - auto iter = m_options->find(option); - - if (iter == m_options->end()) - { - throw_or_mimic(option); - } - parse_option(iter->second, option, arg); } inline bool -ParseResult::consume_positional(const std::string& a) +OptionParser::consume_positional(const std::string& a, PositionalListIterator& next) { - while (m_next_positional != m_positional.end()) + while (next != m_positional.end()) { - auto iter = m_options->find(*m_next_positional); - if (iter != m_options->end()) + auto iter = m_options.find(*next); + if (iter != m_options.end()) { - auto& result = m_results[iter->second]; if (!iter->second->value().is_container()) { + auto& result = m_parsed[iter->second->hash()]; if (result.count() == 0) { - add_to_option(*m_next_positional, a); - ++m_next_positional; + add_to_option(iter, *next, a); + ++next; return true; } - ++m_next_positional; + ++next; continue; } - add_to_option(*m_next_positional, a); + add_to_option(iter, *next, a); return true; } - throw_or_mimic(*m_next_positional); + throw_or_mimic(*next); } return false; @@ -1821,7 +2164,6 @@ void Options::parse_positional(std::vector options) { m_positional = std::move(options); - m_next_positional = m_positional.begin(); m_positional_set.insert(m_positional.begin(), m_positional.end()); } @@ -1835,21 +2177,21 @@ Options::parse_positional(std::initializer_list options) inline ParseResult -Options::parse(int& argc, char**& argv) +Options::parse(int argc, const char* const* argv) { - ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); - return result; + OptionParser parser(*m_options, m_positional, m_allow_unrecognised); + + return parser.parse(argc, argv); } -inline -void -ParseResult::parse(int& argc, char**& argv) +inline ParseResult +OptionParser::parse(int argc, const char* const* argv) { int current = 1; - - int nextKeep = 1; - bool consume_remaining = false; + auto next_positional = m_positional.begin(); + + std::vector unmatched; while (current != argc) { @@ -1859,11 +2201,11 @@ ParseResult::parse(int& argc, char**& argv) ++current; break; } + bool matched = false; + values::parser_tool::ArguDesc argu_desc = + values::parser_tool::ParseArgument(argv[current], matched); - std::match_results result; - std::regex_match(argv[current], result, option_matcher); - - if (result.empty()) + if (!matched) { //not a flag @@ -1876,32 +2218,32 @@ ParseResult::parse(int& argc, char**& argv) //if true is returned here then it was consumed, otherwise it is //ignored - if (consume_positional(argv[current])) + if (consume_positional(argv[current], next_positional)) { } else { - argv[nextKeep] = argv[current]; - ++nextKeep; + unmatched.emplace_back(argv[current]); } //if we return from here then it was parsed successfully, so continue } else { //short or long option? - if (result[4].length() != 0) + if (argu_desc.grouping) { - const std::string& s = result[4]; + const std::string& s = argu_desc.arg_name; for (std::size_t i = 0; i != s.size(); ++i) { std::string name(1, s[i]); - auto iter = m_options->find(name); + auto iter = m_options.find(name); - if (iter == m_options->end()) + if (iter == m_options.end()) { if (m_allow_unrecognised) { + unmatched.push_back(std::string("-") + s[i]); continue; } //error @@ -1919,6 +2261,12 @@ ParseResult::parse(int& argc, char**& argv) { parse_option(value, name, value->value().get_implicit_value()); } + else if (i + 1 < s.size()) + { + std::string arg_value = s.substr(i + 1); + parse_option(value, name, arg_value); + break; + } else { //error @@ -1926,19 +2274,18 @@ ParseResult::parse(int& argc, char**& argv) } } } - else if (result[1].length() != 0) + else if (argu_desc.arg_name.length() != 0) { - const std::string& name = result[1]; + const std::string& name = argu_desc.arg_name; - auto iter = m_options->find(name); + auto iter = m_options.find(name); - if (iter == m_options->end()) + if (iter == m_options.end()) { if (m_allow_unrecognised) { // keep unrecognised options in argument list, skip to next argument - argv[nextKeep] = argv[current]; - ++nextKeep; + unmatched.emplace_back(argv[current]); ++current; continue; } @@ -1949,11 +2296,11 @@ ParseResult::parse(int& argc, char**& argv) auto opt = iter->second; //equals provided for long option? - if (result[2].length() != 0) + if (argu_desc.set_value) { //parse the option given - parse_option(opt, name, result[3]); + parse_option(opt, name, argu_desc.value); } else { @@ -1967,15 +2314,20 @@ ParseResult::parse(int& argc, char**& argv) ++current; } - for (auto& opt : *m_options) + for (auto& opt : m_options) { auto& detail = opt.second; const auto& value = detail->value(); - auto& store = m_results[detail]; + auto& store = m_parsed[detail->hash()]; - if(value.has_default() && !store.count() && !store.has_default()){ - parse_default(detail); + if (value.has_default()) { + if (!store.count() && !store.has_default()) { + parse_default(detail); + } + } + else { + parse_no_value(detail); } } @@ -1983,7 +2335,7 @@ ParseResult::parse(int& argc, char**& argv) { while (current < argc) { - if (!consume_positional(argv[current])) { + if (!consume_positional(argv[current], next_positional)) { break; } ++current; @@ -1991,14 +2343,30 @@ ParseResult::parse(int& argc, char**& argv) //adjust argv for any that couldn't be swallowed while (current != argc) { - argv[nextKeep] = argv[current]; - ++nextKeep; + unmatched.emplace_back(argv[current]); ++current; } } - argc = nextKeep; + finalise_aliases(); + + ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched)); + return parsed; +} + +inline +void +OptionParser::finalise_aliases() +{ + for (auto& option: m_options) + { + auto& detail = *option.second; + auto hash = detail.hash(); + m_keys[detail.short_name()] = hash; + m_keys[detail.long_name()] = hash; + m_parsed.emplace(hash, OptionValue()); + } } inline @@ -2037,6 +2405,11 @@ Options::add_option add_one_option(l, option); } + m_option_list.push_front(*option.get()); + auto iter = m_option_list.begin(); + m_option_map[s] = iter; + m_option_map[l] = iter; + //add the help details auto& options = m_help[group]; @@ -2099,11 +2472,14 @@ Options::help_one_group(const std::string& g) const longest = (std::max)(longest, stringLength(s)); format.push_back(std::make_pair(s, String())); } + longest = (std::min)(longest, OPTION_LONGEST); - longest = (std::min)(longest, static_cast(OPTION_LONGEST)); - - //widest allowed description - auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + //widest allowed description -- min 10 chars for helptext/line + size_t allowed = 10; + if (m_width > allowed + longest + OPTION_DESC_GAP) + { + allowed = m_width - longest - OPTION_DESC_GAP; + } auto fiter = format.begin(); for (const auto& o : group->second.options) @@ -2114,7 +2490,7 @@ Options::help_one_group(const std::string& g) const continue; } - auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion); result += fiter->first; if (stringLength(fiter->first) > longest) @@ -2165,12 +2541,16 @@ void Options::generate_all_groups_help(String& result) const { std::vector all_groups; - all_groups.reserve(m_help.size()); - for (const auto& group : m_help) - { - all_groups.push_back(group.first); - } + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(all_groups), + [] (const std::map::value_type& group) + { + return group.first; + } + ); generate_group_help(result, all_groups); } diff --git a/modules/cxxopts/cxxopts-config.cmake.in b/modules/cxxopts/packaging/cxxopts-config.cmake.in similarity index 100% rename from modules/cxxopts/cxxopts-config.cmake.in rename to modules/cxxopts/packaging/cxxopts-config.cmake.in diff --git a/modules/cxxopts/packaging/pkgconfig.pc.in b/modules/cxxopts/packaging/pkgconfig.pc.in new file mode 100644 index 0000000..9b6a44c --- /dev/null +++ b/modules/cxxopts/packaging/pkgconfig.pc.in @@ -0,0 +1,7 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ +Cflags: -I${includedir} diff --git a/modules/cxxopts/src/CMakeLists.txt b/modules/cxxopts/src/CMakeLists.txt index eec97b7..451d778 100644 --- a/modules/cxxopts/src/CMakeLists.txt +++ b/modules/cxxopts/src/CMakeLists.txt @@ -1,15 +1,15 @@ # Copyright (c) 2014 Jarryd Beck -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -18,7 +18,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -if(CXXOPTS_BUILD_EXAMPLES) - add_executable(example example.cpp) - target_link_libraries(example cxxopts) -endif() +add_executable(example example.cpp) +target_link_libraries(example cxxopts) diff --git a/modules/cxxopts/src/example.cpp b/modules/cxxopts/src/example.cpp index 9b6ea88..c21bad6 100644 --- a/modules/cxxopts/src/example.cpp +++ b/modules/cxxopts/src/example.cpp @@ -27,7 +27,7 @@ THE SOFTWARE. #include "cxxopts.hpp" void -parse(int argc, char* argv[]) +parse(int argc, const char* argv[]) { try { @@ -39,6 +39,8 @@ parse(int argc, char* argv[]) bool apple = false; options + .set_width(70) + .set_tab_expansion() .allow_unrecognised_options() .add_options() ("a,apple", "an apple", cxxopts::value(apple)) @@ -56,6 +58,7 @@ parse(int argc, char* argv[]) ("long-description", "thisisareallylongwordthattakesupthewholelineandcannotbebrokenataspace") ("help", "Print help") + ("tab-expansion", "Tab\texpansion") ("int", "An integer", cxxopts::value(), "N") ("float", "A floating point number", cxxopts::value()) ("vector", "A list of doubles", cxxopts::value>()) @@ -160,7 +163,7 @@ parse(int argc, char* argv[]) } } -int main(int argc, char* argv[]) +int main(int argc, const char* argv[]) { parse(argc, argv); diff --git a/modules/cxxopts/test/CMakeLists.txt b/modules/cxxopts/test/CMakeLists.txt index 1969545..d3467f3 100644 --- a/modules/cxxopts/test/CMakeLists.txt +++ b/modules/cxxopts/test/CMakeLists.txt @@ -1,35 +1,53 @@ -if (CXXOPTS_BUILD_TESTS) - add_executable(options_test main.cpp options.cpp) - target_link_libraries(options_test cxxopts) - add_test(options options_test) +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. - # test if the targets are findable from the build directory - add_test(find-package-test ${CMAKE_CTEST_COMMAND} - -C ${CMAKE_BUILD_TYPE} - --build-and-test - "${CMAKE_CURRENT_SOURCE_DIR}/find-package-test" - "${CMAKE_CURRENT_BINARY_DIR}/find-package-test" - --build-generator ${CMAKE_GENERATOR} - --build-makeprogram ${CMAKE_MAKE_PROGRAM} - --build-options - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" - "-Dcxxopts_DIR=${PROJECT_BINARY_DIR}" - ) +add_executable(options_test main.cpp options.cpp) +target_link_libraries(options_test cxxopts) +add_test(options options_test) - # test if the targets are findable when add_subdirectory is used - add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND} - -C ${CMAKE_BUILD_TYPE} - --build-and-test - "${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test" - "${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test" - --build-generator ${CMAKE_GENERATOR} - --build-makeprogram ${CMAKE_MAKE_PROGRAM} - --build-options - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" - ) +# test if the targets are findable from the build directory +add_test(find-package-test ${CMAKE_CTEST_COMMAND} + -C ${CMAKE_BUILD_TYPE} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/find-package-test" + "${CMAKE_CURRENT_BINARY_DIR}/find-package-test" + --build-generator ${CMAKE_GENERATOR} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" + "-Dcxxopts_DIR=${PROJECT_BINARY_DIR}" +) - add_executable(link_test link_a.cpp link_b.cpp) - target_link_libraries(link_test cxxopts) -endif() +# test if the targets are findable when add_subdirectory is used +add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND} + -C ${CMAKE_BUILD_TYPE} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test" + "${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test" + --build-generator ${CMAKE_GENERATOR} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" +) + +add_executable(link_test link_a.cpp link_b.cpp) +target_link_libraries(link_test cxxopts) diff --git a/modules/cxxopts/test/options.cpp b/modules/cxxopts/test/options.cpp index 5026ea0..610a6bc 100644 --- a/modules/cxxopts/test/options.cpp +++ b/modules/cxxopts/test/options.cpp @@ -8,7 +8,7 @@ class Argv { public: Argv(std::initializer_list args) - : m_argv(new char*[args.size()]) + : m_argv(new const char*[args.size()]) , m_argc(static_cast(args.size())) { int i = 0; @@ -26,7 +26,7 @@ class Argv { } } - char** argv() const { + const char** argv() const { return m_argv.get(); } @@ -36,8 +36,8 @@ class Argv { private: - std::vector> m_args; - std::unique_ptr m_argv; + std::vector> m_args{}; + std::unique_ptr m_argv; int m_argc; }; @@ -69,7 +69,7 @@ TEST_CASE("Basic options", "[options]") "--space", }); - char** actual_argv = argv.argv(); + auto** actual_argv = argv.argv(); auto argc = argv.argc(); auto result = options.parse(argc, actual_argv); @@ -125,7 +125,7 @@ TEST_CASE("No positional", "[positional]") Argv av({"tester", "a", "b", "def"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -154,7 +154,7 @@ TEST_CASE("All positional", "[positional]") auto result = options.parse(argc, argv); - REQUIRE(argc == 1); + CHECK(result.unmatched().size() == 0); REQUIRE(positional.size() == 3); CHECK(positional[0] == "a"); @@ -177,12 +177,12 @@ TEST_CASE("Some positional explicit", "[positional]") Argv av({"tester", "--output", "a", "b", "c", "d"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); - CHECK(argc == 1); + CHECK(result.unmatched().size() == 0); CHECK(result.count("output")); CHECK(result["input"].as() == "b"); CHECK(result["output"].as() == "a"); @@ -203,17 +203,16 @@ TEST_CASE("No positional with extras", "[positional]") Argv av({"extras", "--", "a", "b", "c", "d"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto old_argv = argv; auto old_argc = argc; - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(argc == old_argc - 1); - CHECK(argv[0] == std::string("extras")); - CHECK(argv[1] == std::string("a")); + auto& unmatched = result.unmatched(); + CHECK((unmatched == std::vector{"a", "b", "c", "d"})); } TEST_CASE("Positional not valid", "[positional]") { @@ -226,12 +225,37 @@ TEST_CASE("Positional not valid", "[positional]") { Argv av({"foobar", "bar", "baz"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&); } +TEST_CASE("Positional with empty arguments", "[positional]") { + cxxopts::Options options("positional_with_empty_arguments", "positional with empty argument"); + options.add_options() + ("long", "a long option", cxxopts::value()) + ("program", "program to run", cxxopts::value()) + ("programArgs", "program arguments", cxxopts::value>()) + ; + + options.parse_positional("program", "programArgs"); + + Argv av({"foobar", "--long", "long_value", "--", "someProgram", "ab", "-c", "d", "--ef", "gh", "--ijk=lm", "n", "", "o", }); + std::vector expected({"ab", "-c", "d", "--ef", "gh", "--ijk=lm", "n", "", "o", }); + + auto** argv = av.argv(); + auto argc = av.argc(); + + auto result = options.parse(argc, argv); + auto actual = result["programArgs"].as>(); + + REQUIRE(result.count("program") == 1); + REQUIRE(result["program"].as() == "someProgram"); + REQUIRE(result.count("programArgs") == expected.size()); + REQUIRE(actual == expected); +} + TEST_CASE("Empty with implicit value", "[implicit]") { cxxopts::Options options("empty_implicit", "doesn't handle empty"); @@ -241,7 +265,7 @@ TEST_CASE("Empty with implicit value", "[implicit]") Argv av({"implicit", "--implicit="}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -260,7 +284,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("When no value provided") { Argv av({"no_implicit", "--bool"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::missing_argument_exception&); @@ -269,7 +293,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With equal-separated true") { Argv av({"no_implicit", "--bool=true"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -280,7 +304,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With equal-separated false") { Argv av({"no_implicit", "--bool=false"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -291,7 +315,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With space-separated true") { Argv av({"no_implicit", "--bool", "true"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -302,7 +326,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With space-separated false") { Argv av({"no_implicit", "--bool", "false"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -323,7 +347,7 @@ TEST_CASE("Default values", "[default]") SECTION("Sets defaults") { Argv av({"implicit"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -339,7 +363,7 @@ TEST_CASE("Default values", "[default]") SECTION("When values provided") { Argv av({"implicit", "--default", "5"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -374,7 +398,7 @@ TEST_CASE("Integers", "[options]") Argv av({"ints", "--", "5", "6", "-6", "0", "0xab", "0xAf", "0x0"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -401,7 +425,7 @@ TEST_CASE("Leading zero integers", "[options]") Argv av({"ints", "--", "05", "06", "0x0ab", "0x0001"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -425,7 +449,7 @@ TEST_CASE("Unsigned integers", "[options]") Argv av({"ints", "--", "-2"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -504,7 +528,7 @@ TEST_CASE("Floats", "[options]") Argv av({"floats", "--double", "0.5", "--", "4", "-4", "1.5e6", "-1.5e6"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -529,7 +553,7 @@ TEST_CASE("Invalid integers", "[integer]") { Argv av({"ints", "--", "Ae"}); - char **argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -554,7 +578,7 @@ TEST_CASE("Booleans", "[boolean]") { Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -588,7 +612,7 @@ TEST_CASE("std::vector", "[vector]") { Argv av({"vector", "--vector", "1,-2.1,3,4.5"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse(argc, argv); @@ -609,7 +633,7 @@ TEST_CASE("std::optional", "[optional]") { Argv av({"optional", "--optional", "foo"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse(argc, argv); @@ -632,9 +656,10 @@ TEST_CASE("Unrecognised options", "[options]") { "--long", "-su", "--another_unknown", + "-a", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -643,9 +668,9 @@ TEST_CASE("Unrecognised options", "[options]") { SECTION("After allowing unrecognised options") { options.allow_unrecognised_options(); - CHECK_NOTHROW(options.parse(argc, argv)); - REQUIRE(argc == 3); - CHECK_THAT(argv[1], Catch::Equals("--unknown")); + auto result = options.parse(argc, argv); + auto& unmatched = result.unmatched(); + CHECK((unmatched == std::vector{"--unknown", "-u", "--another_unknown", "-a"})); } } @@ -661,7 +686,7 @@ TEST_CASE("Allow bad short syntax", "[options]") { "-some_bad_short", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -684,7 +709,7 @@ TEST_CASE("Invalid option syntax", "[options]") { "--a", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -704,7 +729,7 @@ TEST_CASE("Options empty", "[options]") { "--unknown" }); auto argc = argv_.argc(); - char** argv = argv_.argv(); + auto** argv = argv_.argv(); CHECK(options.groups().empty()); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&); @@ -733,7 +758,7 @@ TEST_CASE("Initializer list with group", "[options]") { "8000", "-t", }); - char** actual_argv = argv.argv(); + auto** actual_argv = argv.argv(); auto argc = argv.argc(); auto result = options.parse(argc, actual_argv); @@ -763,7 +788,7 @@ TEST_CASE("Option add with add_option(string, Option)", "[options]") { "4" }); auto argc = argv_.argc(); - char** argv = argv_.argv(); + auto** argv = argv_.argv(); auto result = options.parse(argc, argv); CHECK(result.arguments().size()==2); @@ -774,3 +799,34 @@ TEST_CASE("Option add with add_option(string, Option)", "[options]") { CHECK(result["aggregate"].as() == 4); CHECK(result["test"].as() == 5); } + +TEST_CASE("Const array", "[const]") { + const char* const option_list[] = {"empty", "options"}; + cxxopts::Options options("Empty options", " - test constness"); + auto result = options.parse(2, option_list); +} + +TEST_CASE("Parameter follow option", "[parameter]") { + cxxopts::Options options("param_follow_opt", " - test parameter follow option without space."); + options.add_options() + ("j,job", "Job", cxxopts::value>()); + Argv av({"implicit", + "-j", "9", + "--job", "7", + "--job=10", + "-j5", + }); + + auto ** argv = av.argv(); + auto argc = av.argc(); + + auto result = options.parse(argc, argv); + + REQUIRE(result.count("job") == 4); + + auto job_values = result["job"].as>(); + CHECK(job_values[0] == 9); + CHECK(job_values[1] == 7); + CHECK(job_values[2] == 10); + CHECK(job_values[3] == 5); +}