diff --git a/packages/CLI11/.ci/make_and_test.sh b/packages/CLI11/.ci/make_and_test.sh index 2ba81aeeffa627ae010c61162012a7af75d7998b..6aec305ed97680457fc4c16e5b41fb96b64a902c 100755 --- a/packages/CLI11/.ci/make_and_test.sh +++ b/packages/CLI11/.ci/make_and_test.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash echo -en "travis_fold:start:script.build\\r" echo "Building..." +STD=$1 +shift 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 .. -DCLI11_SINGLE_FILE=ON -DCLI11_CXX_STD=$STD -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache $@ cmake --build . -- -j2 set +evx diff --git a/packages/CLI11/.gitmodules b/packages/CLI11/.gitmodules index 2b5117d2cbaf0a48723d833530996594d19a37a3..e9ec356f3475d5a3c7f043752ed93061180a8c51 100644 --- a/packages/CLI11/.gitmodules +++ b/packages/CLI11/.gitmodules @@ -4,3 +4,6 @@ [submodule "extern/sanitizers"] path = extern/sanitizers url = ../../arsenm/sanitizers-cmake +[submodule "extern/json"] + path = extern/json + url = ../../nlohmann/json.git diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 0bd709b9b6e8c72c9f26be4e626c526327955c1e..96c408126ab71f57466b1bfba721bdb7f67d4bda 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,6 +6,6 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = master - commit = 6f41cca9189129fd6df8c82bff532693dcfe52c3 - parent = e7630b332e1d23511b2de021692cf478675a662b + commit = da901cca542612a133efcb04e8e78080186991e4 + parent = 4348958e3bface85aedc9e8a5638f38790834cd5 cmdver = 0.3.1 diff --git a/packages/CLI11/.travis.yml b/packages/CLI11/.travis.yml index 80bdfbfbab0240161469308a5324679ccb2474bd..7e5b645eac588a4c506977be1c9ccfc210193d10 100644 --- a/packages/CLI11/.travis.yml +++ b/packages/CLI11/.travis.yml @@ -11,7 +11,6 @@ branches: - /^v\d/ cache: - ccache: true apt: true directories: - "${TRAVIS_BUILD_DIR}/deps/doxygen" @@ -49,6 +48,7 @@ matrix: - .ci/make_and_test.sh 11 after_success: - export DOXYFILE=$TRAVIS_BUILD_DIR/docs/Doxyfile + - export DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" - | if [ "${TRAVIS_BRANCH}" == "master" ] && [ "${TRAVIS_PULL_REQUEST}" == "false" ] then @@ -56,7 +56,7 @@ matrix: .ci/build_docs.sh fi - # GCC 6 and Coverage + # GCC 7 and coverage (8 does not support lcov, wait till 9 and new lcov) - compiler: gcc env: - GCC_VER=7 @@ -76,9 +76,9 @@ matrix: - ". .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 + - .ci/make_and_test.sh 11 -DCLI11_EXAMPLE_JSON=ON + - .ci/make_and_test.sh 14 -DCLI11_EXAMPLE_JSON=ON + - .ci/make_and_test.sh 17 -DCLI11_EXAMPLE_JSON=ON # GCC 4.7 and Conan - compiler: gcc diff --git a/packages/CLI11/CHANGELOG.md b/packages/CLI11/CHANGELOG.md index d9b05d299e331bbacc5561452d50331d7140f11a..ba405ea012b21239467c9c1d48adf9ca0ee1d55b 100644 --- a/packages/CLI11/CHANGELOG.md +++ b/packages/CLI11/CHANGELOG.md @@ -1,30 +1,67 @@ +## Version 1.6.2: Help-all + +This version fixes some formatting bugs with help-all. It also adds fixes for several warnings, including an experimental optional error on Clang 7. + +* Fixed help-all formatting [#163] + * Printing help-all on nested command now fixed (App) + * Missing space after help-all restored (Default formatter) + * More detail printed on help all (Default formatter) + * Help-all subcommands get indented with inner blank lines removed (Default formatter) + * `detail::find_and_replace` added to utilities +* Fixed CMake install as subproject with `CLI11_INSTALL` flag. [#156] +* Fixed warning about local variable hiding class member with MSVC [#157] +* Fixed compile error with default settings on Clang 7 and libc++ [#158] + +[#156]: https://github.com/CLIUtils/CLI11/issues/156 +[#157]: https://github.com/CLIUtils/CLI11/issues/157 +[#158]: https://github.com/CLIUtils/CLI11/issues/158 +[#163]: https://github.com/CLIUtils/CLI11/pull/163 + +## Version 1.6.1: Platform fixes + +This version provides a few fixes for special cases, such as mixing with `Windows.h` and better defaults +for systems like Hunter. The one new feature is the ability to produce "branded" single file output for +providing custom namespaces or custom macro names. + +* Added fix and test for including Windows.h [#145] +* No longer build single file by default if main project, supports systems stuck on Python 2.6 [#149], [#151] +* Branding support for single file output [#150] + +[#145]: https://github.com/CLIUtils/CLI11/pull/145 +[#149]: https://github.com/CLIUtils/CLI11/pull/149 +[#150]: https://github.com/CLIUtils/CLI11/pull/150 +[#151]: https://github.com/CLIUtils/CLI11/pull/151 + ## Version 1.6: Formatting help Added a new formatting system [#109]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating most formatting code. * Added `CLI::Formatter` and `formatter` slot for apps, inherited. -* `FormatterBase` is the minimum required -* `FormatterLambda` provides for the easy addition of an arbitrary function -* Added `help_all` support (not added by default) +* `FormatterBase` is the minimum required. +* `FormatterLambda` provides for the easy addition of an arbitrary function. +* Added `help_all` support (not added by default). Changes to the help system (most normal users will not notice this): -* Renamed `single_name` to `get_name(false, false)` (the default) -* The old `get_name()` is now `get_name(false, true)` -* The old `get_pname()` is now `get_name(true, false)` -* Removed `help_*` functions -* Protected function `_has_help_positional` removed -* `format_help` can now be chained +* Renamed `single_name` to `get_name(false, false)` (the default). +* The old `get_name()` is now `get_name(false, true)`. +* The old `get_pname()` is now `get_name(true, false)`. +* Removed `help_*` functions. +* Protected function `_has_help_positional` removed. +* `format_help` can now be chained. +* Added getters for the missing parts of options (help no longer uses any private parts). +* Help flags now use new `short_circuit` property to simplify parsing. [#121] New for Config file reading and writing [#121]: -* Overridable, bidirectional Config -* ConfigINI provided and used by default -* Renamed ini to config in many places -* Has `config_formatter()` and `get_config_formatter()` -* Dropped prefix argument from `config_to_str` -* Added `ConfigItem` +* Overridable, bidirectional Config. +* ConfigINI provided and used by default. +* Renamed ini to config in many places. +* Has `config_formatter()` and `get_config_formatter()`. +* Dropped prefix argument from `config_to_str`. +* Added `ConfigItem`. +* Added an example of a custom config format using [nlohmann/json]. [#138] Validators are now much more powerful [#118], all built in validators upgraded to the new form: @@ -36,20 +73,29 @@ Validators are now much more powerful [#118], all built in validators upgraded t Other changes: -* Added `->each()` to make adding custom callbacks easier [#126] -* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering -* Added `get_groups()` to get groups -* Added getters for the missing parts of options (help no longer uses any private parts) -* Better support for manual options with `get_option`, `set_results`, and `empty` [#119] -* `lname` and `sname` have getters, added `const get_parent` [#120] -* Using `add_set` will now capture L-values for sets, allowing further modification [#113] -* Internally, `type_name` is now a lambda function; for sets, this reads the set live [#116] -* Dropped duplicate way to run `get_type_name` (`get_typeval`) +* Fixing `parse(args)`'s `args` setting and ordering after parse. [#141] +* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. [#136] +* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val`. [#136] +* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name`. [#136] +* Fixed support `N<-1` for `type_size`. [#140] +* Added `->each()` to make adding custom callbacks easier. [#126] +* Allow empty options `add_option("-n",{})` to be edited later with `each` [#142] +* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering. +* Added `get_groups()` to get groups. +* Better support for manual options with `get_option`, `set_results`, and `empty`. [#119] +* `lname` and `sname` have getters, added `const get_parent`. [#120] +* Using `add_set` will now capture L-values for sets, allowing further modification. [#113] +* Dropped duplicate way to run `get_type_name` (`get_typeval`). +* Removed `requires` in favor of `needs` (deprecated in last version). [#112] +* Const added to argv. [#126] + +Backend and testing changes: + +* Internally, `type_name` is now a lambda function; for sets, this reads the set live. [#116] +* Cleaner tests without `app.reset()` (and `reset` is now `clear`). [#141] +* Better CMake policy handling. [#110] +* Includes are properly sorted. [#120] * Testing (only) now uses submodules. [#111] -* Removed `requires` in favor of `needs` (deprecated in last version) [#112] -* Better CMake policy handling [#110] -* Includes are properly sorted [#120] -* Help flags now use new `short_circuit` property to simplify parsing [#121] [#109]: https://github.com/CLIUtils/CLI11/pull/109 [#110]: https://github.com/CLIUtils/CLI11/pull/110 @@ -62,6 +108,13 @@ Other changes: [#120]: https://github.com/CLIUtils/CLI11/pull/120 [#121]: https://github.com/CLIUtils/CLI11/pull/121 [#126]: https://github.com/CLIUtils/CLI11/pull/126 +[#127]: https://github.com/CLIUtils/CLI11/pull/127 +[#138]: https://github.com/CLIUtils/CLI11/pull/138 +[#140]: https://github.com/CLIUtils/CLI11/pull/140 +[#141]: https://github.com/CLIUtils/CLI11/pull/141 +[#142]: https://github.com/CLIUtils/CLI11/pull/142 + +[nlohmann/json]: https://github.com/nlohmann/json ### Version 1.5.4: Optionals This version fixes the optional search in the single file version; some macros were not yet defined when it did the search. You can define the `CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search. diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index 1fe8248365dcab530e9d2fc6ac56a6a837e2599c..79404b8deeac7c5fa999644355d8ecac5b4158de 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -1,7 +1,13 @@ -cmake_minimum_required(VERSION 3.4...3.11) +cmake_minimum_required(VERSION 3.4) +# Note: this is a header only library. If you have an older CMake than 3.4, +# just add the CLI11/include directory and that's all you need to do. +# Make sure users don't get warnings on a tested (3.4 to 3.12) version +# of CMake. For most of the policies, the new version is better (hence the change). if(${CMAKE_VERSION} VERSION_LESS 3.12) - cmake_policy(VERSION ${CMAKE_VERSION}) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.12) endif() set(VERSION_REGEX "#define CLI11_VERSION[ \t]+\"(.+)\"") @@ -13,10 +19,9 @@ file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/Version.hpp" # Pick out just the version string(REGEX REPLACE ${VERSION_REGEX} "\\1" VERSION_STRING "${VERSION_STRING}") +# Add the project project(CLI11 LANGUAGES CXX VERSION ${VERSION_STRING}) -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 @@ -31,7 +36,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(MSVC) add_definitions("/W4") else() - add_definitions(-Wall -Wextra -pedantic -Wno-deprecated-declarations) + add_definitions(-Wall -Wextra -pedantic) endif() if(CMAKE_VERSION VERSION_GREATER 3.6) @@ -74,8 +79,16 @@ target_include_directories(CLI11 INTERFACE # Make add_subdirectory work like find_package add_library(CLI11::CLI11 ALIAS CLI11) +option(CLI11_INSTALL "Install the CLI11 folder to include during install process" ${CUR_PROJ}) + # This folder should be installed -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/CLI DESTINATION include) +if(CLI11_INSTALL) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/CLI DESTINATION include) + + # Make an export target + install(TARGETS CLI11 + EXPORT CLI11Targets) +endif() # Use find_package on the installed package # Since we have no custom code, we can directly write this @@ -90,37 +103,42 @@ write_basic_package_version_file( COMPATIBILITY AnyNewerVersion ) -# Make version available in the install -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/CLI11ConfigVersion.cmake" - DESTINATION lib/cmake/CLI11) - -# Make an export target -install(TARGETS CLI11 - EXPORT CLI11Targets) - -# Install the export target as a file -install(EXPORT CLI11Targets - FILE CLI11Config.cmake - NAMESPACE CLI11:: - DESTINATION lib/cmake/CLI11) - -# Use find_package on the installed package -export(TARGETS CLI11 - NAMESPACE CLI11:: - FILE CLI11Targets.cmake) +# These installs only make sense for a local project +if(CUR_PROJ) + # Make version available in the install + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/CLI11ConfigVersion.cmake" + DESTINATION lib/cmake/CLI11) + + # Install the export target as a file + install(EXPORT CLI11Targets + FILE CLI11Config.cmake + NAMESPACE CLI11:: + DESTINATION lib/cmake/CLI11) + + # Use find_package on the installed package + export(TARGETS CLI11 + NAMESPACE CLI11:: + FILE CLI11Targets.cmake) + + # Register in the user cmake package registry + export(PACKAGE CLI11) +endif() -# Register in the user cmake package registry -export(PACKAGE CLI11) +option(CLI11_SINGLE_FILE "Generate a single header file" OFF) +if(CLI11_SINGLE_FILE) # Single file test -find_package(PythonInterp) -cmake_dependent_option(CLI11_SINGLE_FILE "Generate a single header file" ON "CUR_PROJ;PYTHONINTERP_FOUND" OFF) + if(CMAKE_VERSION VERSION_LESS 3.12) + find_package(PythonInterp REQUIRED) + set(Python_VERSION ${PYTHON_VERSION_STRING}) + set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}") + else() + find_package(Python REQUIRED) + endif() -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" + 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" ${CLI11_headers} ) add_custom_target(generate_cli_single_file ALL diff --git a/packages/CLI11/LICENSE b/packages/CLI11/LICENSE index d3d8f0122469ff0545f94dee5ac22854975e5131..6b00778361fa1a72bf82271e2a56ba166204b540 100644 --- a/packages/CLI11/LICENSE +++ b/packages/CLI11/LICENSE @@ -1,4 +1,4 @@ -CLI11 1.5 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry +CLI11 1.6 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry Schreiner under NSF AWARD 1414736. All rights reserved. Redistribution and use in source and binary forms of CLI11, with or without diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index 10c50049df5145d25f11b012e160b1a37b2d667a..319769ff0943e40d905356886b2ececac1c7fe12 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -1,70 +1,105 @@ -[![Build Status Linux and macOS][travis-badge]][Travis] -[![Build Status Windows][appveyor-badge]][AppVeyor] -[![Code Coverage][codecov-badge]][CodeCov] +# CLI11: Command line parser for C++11 + + + +[![Build Status Linux and macOS][travis-badge]][travis] +[![Build Status Windows][appveyor-badge]][appveyor] +[![Code Coverage][codecov-badge]][codecov] [![Codacy Badge][codacy-badge]][codacy-link] [![Join the chat at https://gitter.im/CLI11gitter/Lobby][gitter-badge]][gitter] [![License: BSD][license-badge]](./LICENSE) -[![Latest release][releases-badge]][Github Releases] -[![DOI][DOI-badge]][DOI-link] +[![Latest release][releases-badge]][github releases] +[![DOI][doi-badge]][doi-link] [![Conan.io][conan-badge]][conan-link] -[![Try CLI11 1.5.2 online][wandbox-badge]][wandbox-link] +[![Try CLI11 1.6 online][wandbox-badge]][wandbox-link] -[Documentation][GitBook] • +[Documentation][gitbook] • [API Reference][api-docs] • [What's new](./CHANGELOG.md) -# CLI11: Command line parser for C++11 +CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface. + +## Table of Contents + +- [Background](#background) + - [Introduction](#introduction) + - [Why write another CLI parser?](#why-write-another-cli-parser) + - [Other parsers](#other-parsers) + - [Features not supported by this library](#features-not-supported-by-this-library) +- [Install](#install) +- [Usage](#usage) + - [Adding options](#adding-options) + - [Option types](#option-types) + - [Option options](#option-options) + - [Subcommands](#subcommands) + - [Subcommand options](#subcommand-options) + - [Configuration file](#configuration-file) + - [Inheriting defaults](#inheriting-defaults) + - [Formatting](#formatting) + - [Subclassing](#subclassing) + - [How it works](#how-it-works) + - [Utilities](#utilities) + - [Other libraries](#other-libraries) +- [API](#api) +- [Contribute](#contribute) +- [License](#license) + +## Background + +### Introduction CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks. -It is tested on [Travis] and [AppVeyor], and is being included in the [GooFit GPU fitting framework][GooFit]. It was inspired by [`plumbum.cli`][Plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook], as well as [API documentation][api-docs] generated by Travis. -See the [changelog](./CHANGELOG.md) or [GitHub Releases] for details for current and past releases. Also see the [Version 1.0 post] and [Version 1.3 post] for more information. +It is tested on [Travis][] and [AppVeyor][], and is being included in the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis. +See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], or [Version 1.6 post][] for more information. -You can be notified when new releases are made by subscribing to https://github.com/CLIUtils/CLI11/releases.atom on an RSS reader, like Feedly. +You can be notified when new releases are made by subscribing to <https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly. ### Why write another CLI parser? An acceptable CLI parser library should be all of the following: -* Easy to include (i.e., header only, one file if possible, **no external requirements**). -* Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. -* C++11 or better: Should work with GCC 4.7+ (such as GCC 4.8 on CentOS 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. -* Work on Linux, macOS, and Windows. -* Well tested using [Travis] (Linux and macOS) and [AppVeyor] (Windows). "Well" is defined as having good coverage measured by [CodeCov]. -* Clear help printing. -* Nice error messages. -* Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. -* Easy to execute, with help, parse errors, etc. providing correct exit and details. -* Easy to extend as part of a framework that provides "applications" to users. -* Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later). -* Ability to add a configuration file (`ini` format), and produce it as well. -* Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications. -* Work with standard types, simple custom types, and extendible to exotic types. -* Permissively licensed. +- Easy to include (i.e., header only, one file if possible, **no external requirements**). +- Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. +- C++11 or better: Should work with GCC 4.7+ (such as GCC 4.8 on CentOS 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. +- Work on Linux, macOS, and Windows. +- Well tested using [Travis][] (Linux and macOS) and [AppVeyor][] (Windows). "Well" is defined as having good coverage measured by [CodeCov][]. +- Clear help printing. +- Nice error messages. +- Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. +- Easy to execute, with help, parse errors, etc. providing correct exit and details. +- Easy to extend as part of a framework that provides "applications" to users. +- Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later). +- Ability to add a configuration file (`ini` format), and produce it as well. +- Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications. +- Work with standard types, simple custom types, and extendible to exotic types. +- Permissively licensed. + +### Other parsers <details><summary>The major CLI parsers for C++ include, with my biased opinions: (click to expand)</summary><p> -| Library | My biased opinion | -|---------|-------------------| -| [Boost Program Options] | A great library if you already depend on Boost, but its pre-C++11 syntax is really odd and setting up the correct call in the main function is poorly documented (and is nearly a page of code). A simple wrapper for the Boost library was originally developed, but was discarded as CLI11 became more powerful. The idea of capturing a value and setting it originated with Boost PO. [See this comparison.][cli11-po-compare] | -| [The Lean Mean C++ Option Parser] | One header file is great, but the syntax is atrocious, in my opinion. It was quite impractical to wrap the syntax or to use in a complex project. It seems to handle standard parsing quite well. | -| [TCLAP] | The not-quite-standard command line parsing causes common shortcuts to fail. It also seems to be poorly supported, with only minimal bugfixes accepted. Header only, but in quite a few files. Has not managed to get enough support to move to GitHub yet. No subcommands. Produces wrapped values. | -| [Cxxopts] | C++11, single file, and nice CMake support, but requires regex, therefore GCC 4.8 (CentOS 7 default) does not work. Syntax closely based on Boost PO, so not ideal but familiar. | -| [DocOpt] | Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized. | +| Library | My biased opinion | +| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Boost Program Options][] | A great library if you already depend on Boost, but its pre-C++11 syntax is really odd and setting up the correct call in the main function is poorly documented (and is nearly a page of code). A simple wrapper for the Boost library was originally developed, but was discarded as CLI11 became more powerful. The idea of capturing a value and setting it originated with Boost PO. [See this comparison.][cli11-po-compare] | +| [The Lean Mean C++ Option Parser][] | One header file is great, but the syntax is atrocious, in my opinion. It was quite impractical to wrap the syntax or to use in a complex project. It seems to handle standard parsing quite well. | +| [TCLAP][] | The not-quite-standard command line parsing causes common shortcuts to fail. It also seems to be poorly supported, with only minimal bugfixes accepted. Header only, but in quite a few files. Has not managed to get enough support to move to GitHub yet. No subcommands. Produces wrapped values. | +| [Cxxopts][] | C++11, single file, and nice CMake support, but requires regex, therefore GCC 4.8 (CentOS 7 default) does not work. Syntax closely based on Boost PO, so not ideal but familiar. | +| [DocOpt][] | Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized. | After I wrote this, I also found the following libraries: -| Library | My biased opinion | -|---------|-------------------| -| [GFlags] | The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars. | -| [GetOpt] | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors). | -| [ProgramOptions.hxx] | Intresting library, less powerful and no subcommands. Nice callback system. | -| [Args] | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose. | -| [Argument Aggregator] | I'm a big fan of the [fmt] library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands. | -| [Clara] | Simple library built for the excellent [Catch] testing framework. Unique syntax, limited scope. | -| [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. | +| Library | My biased opinion | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [GFlags][] | The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars. | +| [GetOpt][] | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors). | +| [ProgramOptions.hxx][] | Intresting library, less powerful and no subcommands. Nice callback system. | +| [Args][] | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose. | +| [Argument Aggregator][] | I'm a big fan of the [fmt][] library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands. | +| [Clara][] | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope. | +| [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. You can also find other single file libraries at [Single file libs]. +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/> @@ -72,26 +107,25 @@ See [Awesome C++] for a less-biased list of parsers. You can also find other sin None of these libraries fulfill all the above requirements, or really even come close. As you probably have already guessed, CLI11 does. So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss. -## Features not supported by this library +### Features not supported by this library There are some other possible "features" that are intentionally not supported by this library: -* Non-standard variations on syntax, like `-long` options. This is non-standard and should be avoided, so that is enforced by this library. -* Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw. -* In C++14, you could have a set of `callback` methods with differing signatures (tested in a branch). Not deemed worth having a C++14 variation on API and removed. -* Autocomplete: This might eventually be added to both Plumbum and CLI11, but it is not supported yet. -* Wide strings / unicode: Since this uses the standard library only, it might be hard to properly implement, but I would be open to suggestions in how to do this. - +- Non-standard variations on syntax, like `-long` options. This is non-standard and should be avoided, so that is enforced by this library. +- Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw. +- In C++14, you could have a set of `callback` methods with differing signatures (tested in a branch). Not deemed worth having a C++14 variation on API and removed. +- Autocomplete: This might eventually be added to both Plumbum and CLI11, but it is not supported yet. +- Wide strings / unicode: Since this uses the standard library only, it might be hard to properly implement, but I would be open to suggestions in how to do this. -## Installing +## Install To use, there are two methods: -1. Copy `CLI11.hpp` from the [most recent release][Github Releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. -2. Use `CLI/*.hpp` files. You could check out the repository as a submodule, for example. You can use the `CLI11::CLI11` interface target when linking from `add_subdirectory`. - You can also configure and optionally install the project, and then use `find_package(CLI11 CONFIG)` to get the `CLI11::CLI11` target. You can also use [Conan.io][conan-link] or [Hunter]. - (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and - using C++11 is all you really need.) +1. Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. +2. Use `CLI/*.hpp` files. You could check out the repository as a submodule, for example. You can use the `CLI11::CLI11` interface target when linking from `add_subdirectory`. + You can also configure and optionally install the project, and then use `find_package(CLI11 CONFIG)` to get the `CLI11::CLI11` target. You can also use [Conan.io][conan-link] or [Hunter][]. + (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and + using C++11 is all you really need.) To build the tests, checkout the repository and use CMake: @@ -103,7 +137,9 @@ make GTEST_COLOR=1 CTEST_OUTPUT_ON_FAILURE=1 make test ``` -## Adding options +## Usage + +### Adding options To set up, add options, and run, your main function will look something like this: @@ -131,10 +167,11 @@ The try/catch block ensures that `-h,--help` or a parse error will exit with the </p></details> </br> - The initialization is just one line, adding options is just two each. The parse macro is just one line (or 5 for the contents of the macro). After the app runs, the filename will be set to the correct value if it was passed, otherwise it will be set to the default. You can check to see if this was passed on the command line with `app.count("--file")`. -The supported values are: +#### Option types + +While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are: ```cpp app.add_option(option_name, @@ -160,55 +197,58 @@ app.add_set(option_name, app.add_set_ignore_case(... // String only -App* subcom = app.add_subcommand(name, discription); +App* subcom = app.add_subcommand(name, description); ``` An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option` or `add_set`. The set options allow your users to pick from a set of predefined options; you can add an existing set if you need to modify the set later, or you can use an initializer list. 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 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals] for information on how this was done and how you can add your own converters. +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 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters. -### Example +#### 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` -* `"this"` Can only be passed positionally -* `"-a,-b,-c"` No limit to the number of non-positional option names +- `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` +- `"this"` Can only be passed positionally +- `"-a,-b,-c"` No limit to the number of non-positional option names +The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup. -The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup. Before parsing, you can set the following options: +#### Option options -* `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. -* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option. -* `->set_custom_option(typename, N)`: Set the name and (optional) intrinsic size of an option. The parser will require multiples of this number if negative. -* `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. -* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. -* `->envname(name)`: Gets the value from the environment if present and not passed on the command line. -* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). -* `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). -* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which always default to take last). -* `->check(CLI::ExistingFile)`: Requires that the file exists if given. -* `->check(CLI::ExistingDirectory)`: Requires that the directory exists. -* `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists. -* `->check(CLI::NonexistentPath)`: Requires that the path does not exist. -* `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. -* `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options. -* `->each(void(std::string)>`: Run this function on each value received, as it is received. -* `->configurable(false)`: Disable this option from being in a configuration file. +Before parsing, you can set the following options: -These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort). +- `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. +- `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option. +- `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead) +- `->type_size(N)`: Set the intrinsic size of an option. The parser will require multiples of this number if negative. +- `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. +- `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. +- `->envname(name)`: Gets the value from the environment if present and not passed on the command line. +- `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). +- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). +- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which always default to take last). +- `->check(CLI::ExistingFile)`: Requires that the file exists if given. +- `->check(CLI::ExistingDirectory)`: Requires that the directory exists. +- `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists. +- `->check(CLI::NonexistentPath)`: Requires that the path does not exist. +- `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. +- `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options. +- `->each(void(std::string)>`: Run this function on each value received, as it is received. +- `->configurable(false)`: Disable this option from being in a configuration file. +These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort). On the command line, options can be given as: -* `-a` (flag) -* `-abc` (flags can be combined) -* `-f filename` (option) -* `-ffilename` (no space required) -* `-abcf filename` (flags and option can be combined) -* `--long` (long flag) -* `--file filename` (space) -* `--file=filename` (equals) +- `-a` (flag) +- `-abc` (flags can be combined) +- `-f filename` (option) +- `-ffilename` (no space required) +- `-abcf filename` (flags and option can be combined) +- `--long` (long flag) +- `--file filename` (space) +- `--file=filename` (equals) Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). @@ -217,9 +257,7 @@ You can access a vector of pointers to the parsed options in the original order If `--` is present in the command line that does not end an unlimited option, then everything after that is positional only. - - -## Subcommands +### Subcommands Subcommands are supported, and can be nested infinitely. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including ignore case). @@ -231,41 +269,43 @@ subcommand name from matching. If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool). All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an `App` pointer or a string name was collected on the command line. -For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.set_callback`. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can +For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.callback`. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can even exit the program through the callback. The main `App` has a callback slot, as well, but it is generally not as useful. You are allowed to throw `CLI::Success` in the callbacks. -Multiple subcommands are allowed, to allow [`Click`][Click] like series of commands (order is preserved). +Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). + +#### Subcommand options There are several options that are supported on the main app and subcommands. These are: -* `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. -* `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. -* `.require_subcommand()`: Require 1 or more subcommands. -* `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more. -* `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. -* `.add_subcommand(name, description="")` Add a subcommand, returns a pointer to the internally stored subcommand. -* `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. -* `.get_subcommands(filter)`: The list of subcommands given on the command line. -* `.get_parent()`: Get the parent App or nullptr if called on master App. -* `.get_option(name)`: Get an option pointer by option name -* `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats). -* `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). -* `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details. -* `.get_description()`: Access the description. -* `.parsed()`: True if this subcommand was given on the command line. -* `.set_name(name)`: Add or change the name. -* `.set_callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. -* `.allow_extras()`: Do not throw an error if extra arguments are left over. -* `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognised item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. -* `.set_footer(message)`: Set text to appear at the bottom of the help string. -* `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option. -* `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands. -* `.set_failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). -* `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand. +- `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. +- `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. +- `.require_subcommand()`: Require 1 or more subcommands. +- `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more. +- `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. +- `.add_subcommand(name, description="")` Add a subcommand, returns a pointer to the internally stored subcommand. +- `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. +- `.get_subcommands(filter)`: The list of subcommands given on the command line. +- `.get_parent()`: Get the parent App or nullptr if called on master App. +- `.get_option(name)`: Get an option pointer by option name +- `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats). +- `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). +- `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details. +- `.get_description()`: Access the description. +- `.parsed()`: True if this subcommand was given on the command line. +- `.name(name)`: Add or change the name. +- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. +- `.allow_extras()`: Do not throw an error if extra arguments are left over. +- `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognised item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. +- `.footer(message)`: Set text to appear at the bottom of the help string. +- `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option. +- `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands. +- `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). +- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand. > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function. -## Configuration file +### Configuration file ```cpp app.set_config(option_name="", @@ -283,17 +323,20 @@ If this is called with no arguments, it will remove the configuration file optio value = 1 str = "A string" vector = 1 2 3 +str_vector = "one" "two" "and three" -; Section map to subcommands +; Sections map to subcommands [subcommand] in_subcommand = Wow sub.subcommand = true ``` -Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". To print a configuration file from the passed +Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". You cannot set positional-only arguments or force subcommands to be present in the command line. + +To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. -## Inheriting defaults +### Inheriting defaults Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. @@ -306,7 +349,7 @@ app.option_defaults()->required(); The default settings for options are inherited to subcommands, as well. -## Formatting +### Formatting The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature. CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do @@ -315,18 +358,17 @@ that too; you just need to implement the correct signature. The first argument i The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the situation the help was called in. `Sub` is optional, but the default formatter uses it to make sure expanded subcommands are called with their own formatter since you can't access anything but the call operator once a formatter has been set. -## Subclassing +### Subclassing The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can also set a help-all flag with `.set_help_all_flag(name, help_string)`; this will expand the subcommands (one level only). You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse but before run behavior, while -still giving the user freedom to `set_callback` on the main app. +still giving the user freedom to `callback` on the main app. The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `unique_ptr`s (like `Option`s) and are deleted when the main `App` goes out of scope. - -## How it works +### 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. @@ -335,7 +377,7 @@ Other values can be added as long as they support `operator>>` (and defaults can 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 +#### Example ```cpp app.add_option("--fancy-count", [](std::vector<std::string> val){ @@ -343,7 +385,7 @@ app.add_option("--fancy-count", [](std::vector<std::string> val){ }); ``` -## Utilities +### Utilities There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output. @@ -357,9 +399,9 @@ some_long_running_process(); This will create a timer with a title (default: `Timer`), and will customize the output using the predefined `Big` output (default: `Simple`). Because it is an `AutoTimer`, it will print out the time elapsed when the timer is destroyed at the end of the block. If you use `Timer` instead, you can use `to_string` or `std::cout << timer << std::endl;` to print the time. The print function can be any function that takes two strings, the title and the time, and returns a formatted string for printing. -## Other libraries +### Other libraries -If you use the excellent [Rang] library to add color to your terminal in a safe, multi-platform way, you can combine it with CLI11 nicely: +If you use the excellent [Rang][] library to add color to your terminal in a safe, multi-platform way, you can combine it with CLI11 nicely: ```cpp std::atexit([](){std::cout << rang::style::reset;}); @@ -395,79 +437,86 @@ And, in your main function: sigaction(SIGINT, &sigIntHandler, nullptr); ``` -## Contributing +## API -To contribute, open an [issue][Github Issues] or [pull request][Github Pull Requests] on GitHub, or ask a question on [gitter]. The is also a short note to contributors [here](./CONTRIBUTING.md). +The API is [documented here][api-docs]. Also see the [CLI11 tutorial GitBook][gitbook]. -As of version 1.0, this library is available under a 3-Clause BSD license. See the [LICENSE](./LICENSE) file for details. +## Contribute + +To contribute, open an [issue][github issues] or [pull request][github pull requests] on GitHub, or ask a question on [gitter][]. The is also a short note to contributors [here](./.github/CONTRIBUTING.md). +This readme roughly follows the [Standard Readme Style][] and includes a mention of almost every feature of the library. More complex features are documented in more detail in the [CLI11 tutorial GitBook][gitbook]. This project was created by [Henry Schreiner](https://github.com/henryiii). Significant features and/or improvements to the code were contributed by: -* [Marcus Brinkmann](https://github.com/lambdafu) -* [Jonas Nilsson](https://github.com/SkyToGround) -* [Doug Johnston](https://github.com/dvj) -* [Lucas Czech](https://github.com/lczech) -* [Mathias Soeken](https://github.com/msoeken) -* [Nathan Hourt](https://github.com/nathanhourt) -* [Stéphane Del Pino](https://github.com/delpinux) -* [Anton](https://github.com/SX91) - -CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] library under [NSF Award 1414736]. Version 0.9 was featured in a [DIANA/HEP] meeting at CERN ([see the slides][DIANA slides]). Please give it a try! Feedback is always welcome. - - -[DOI-badge]: https://zenodo.org/badge/80064252.svg -[DOI-link]: https://zenodo.org/badge/latestdoi/80064252 -[travis-badge]: https://img.shields.io/travis/CLIUtils/CLI11/master.svg?label=Linux/macOS -[Travis]: https://travis-ci.org/CLIUtils/CLI11 -[appveyor-badge]: https://img.shields.io/appveyor/ci/HenrySchreiner/cli11/master.svg?label=Windows -[AppVeyor]: https://ci.appveyor.com/project/HenrySchreiner/cli11 -[codecov-badge]: https://codecov.io/gh/CLIUtils/CLI11/branch/master/graph/badge.svg -[CodeCov]: https://codecov.io/gh/CLIUtils/CLI11 -[gitter-badge]: https://badges.gitter.im/CLI11gitter/Lobby.svg -[gitter]: https://gitter.im/CLI11gitter/Lobby -[license-badge]: https://img.shields.io/badge/License-BSD-blue.svg -[conan-badge]: https://api.bintray.com/packages/cliutils/CLI11/CLI11%3Acliutils/images/download.svg -[conan-link]: https://bintray.com/cliutils/CLI11/CLI11%3Acliutils/_latestVersion -[Github Releases]: https://github.com/CLIUtils/CLI11/releases -[Github Issues]: https://github.com/CLIUtils/CLI11/issues -[Github Pull Requests]: https://github.com/CLIUtils/CLI11/pulls -[GooFit]: https://GooFit.github.io -[Plumbum]: https://plumbum.readthedocs.io/en/latest/ -[Click]: http://click.pocoo.org -[api-docs]: https://CLIUtils.github.io/CLI11/index.html -[Rang]: https://github.com/agauniyal/rang -[Boost Program Options]: http://www.boost.org/doc/libs/1_63_0/doc/html/program_options.html -[The Lean Mean C++ Option Parser]: http://optionparser.sourceforge.net -[TCLAP]: http://tclap.sourceforge.net -[Cxxopts]: https://github.com/jarro2783/cxxopts -[DocOpt]: https://github.com/docopt/docopt.cpp -[ROOT]: https://root.cern.ch -[cltools-cmake]: https://github.com/CLIUtils/cmake -[GFlags]: https://gflags.github.io/gflags -[GetOpt]: https://www.gnu.org/software/libc/manual/html_node/Getopt.html -[DIANA/HEP]: http://diana-hep.org -[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 -[Argh!]: https://github.com/adishavit/argh -[fmt]: https://github.com/fmtlib/fmt -[Catch]: https://github.com/philsquared/Catch -[Clara]: https://github.com/philsquared/Clara -[Version 1.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-10/ -[Version 1.3 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-13/ -[wandbox-badge]: https://img.shields.io/badge/try-online-blue.svg -[wandbox-link]: https://wandbox.org/permlink/Z4uwGhnhD2wm2r7Z -[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 -[CLI]: https://codesynthesis.com/projects/cli/ -[Single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md -[codacy-badge]: https://api.codacy.com/project/badge/Grade/ac0df3aead2a4421b02070c3f324a0b9 -[codacy-link]: https://www.codacy.com/app/henryiii/CLI11?utm_source=github.com&utm_medium=referral&utm_content=CLIUtils/CLI11&utm_campaign=Badge_Grade -[Hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html +- [Anton](https://github.com/SX91) +- [Doug Johnston](https://github.com/dvj) +- [Jonas Nilsson](https://github.com/SkyToGround) +- [Lucas Czech](https://github.com/lczech) +- [Marcus Brinkmann](https://github.com/lambdafu) +- [Mathias Soeken](https://github.com/msoeken) +- [Nathan Hourt](https://github.com/nathanhourt) +- [Sean Fisk](https://github.com/seanfisk) +- [Stéphane Del Pino](https://github.com/delpinux) + +## License + +As of version 1.0, this library is available under a 3-Clause BSD license. See the [LICENSE](./LICENSE) file for details. + +CLI11 was developed at the [University of Cincinnati][] to support of the [GooFit][] library under [NSF Award 1414736][]. Version 0.9 was featured in a [DIANA/HEP][] meeting at CERN ([see the slides][diana slides]). Please give it a try! Feedback is always welcome. + +[doi-badge]: https://zenodo.org/badge/80064252.svg +[doi-link]: https://zenodo.org/badge/latestdoi/80064252 +[travis-badge]: https://img.shields.io/travis/CLIUtils/CLI11/master.svg?label=Linux/macOS +[travis]: https://travis-ci.org/CLIUtils/CLI11 +[appveyor-badge]: https://img.shields.io/appveyor/ci/HenrySchreiner/cli11/master.svg?label=Windows +[appveyor]: https://ci.appveyor.com/project/HenrySchreiner/cli11 +[codecov-badge]: https://codecov.io/gh/CLIUtils/CLI11/branch/master/graph/badge.svg +[codecov]: https://codecov.io/gh/CLIUtils/CLI11 +[gitter-badge]: https://badges.gitter.im/CLI11gitter/Lobby.svg +[gitter]: https://gitter.im/CLI11gitter/Lobby +[license-badge]: https://img.shields.io/badge/License-BSD-blue.svg +[conan-badge]: https://api.bintray.com/packages/cliutils/CLI11/CLI11%3Acliutils/images/download.svg +[conan-link]: https://bintray.com/cliutils/CLI11/CLI11%3Acliutils/_latestVersion +[github releases]: https://github.com/CLIUtils/CLI11/releases +[github issues]: https://github.com/CLIUtils/CLI11/issues +[github pull requests]: https://github.com/CLIUtils/CLI11/pulls +[goofit]: https://GooFit.github.io +[plumbum]: https://plumbum.readthedocs.io/en/latest/ +[click]: http://click.pocoo.org +[api-docs]: https://CLIUtils.github.io/CLI11/index.html +[rang]: https://github.com/agauniyal/rang +[boost program options]: http://www.boost.org/doc/libs/1_63_0/doc/html/program_options.html +[the lean mean c++ option parser]: http://optionparser.sourceforge.net +[tclap]: http://tclap.sourceforge.net +[cxxopts]: https://github.com/jarro2783/cxxopts +[docopt]: https://github.com/docopt/docopt.cpp +[gflags]: https://gflags.github.io/gflags +[getopt]: https://www.gnu.org/software/libc/manual/html_node/Getopt.html +[diana/hep]: http://diana-hep.org +[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 +[argh!]: https://github.com/adishavit/argh +[fmt]: https://github.com/fmtlib/fmt +[catch]: https://github.com/philsquared/Catch +[clara]: https://github.com/philsquared/Clara +[version 1.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-10/ +[version 1.3 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-13/ +[version 1.6 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-16/ +[wandbox-badge]: https://img.shields.io/badge/try-online-blue.svg +[wandbox-link]: https://wandbox.org/permlink/95DStlyHsquYH3k6 +[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 +[cli]: https://codesynthesis.com/projects/cli/ +[single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md +[codacy-badge]: https://api.codacy.com/project/badge/Grade/ac0df3aead2a4421b02070c3f324a0b9 +[codacy-link]: https://www.codacy.com/app/henryiii/CLI11?utm_source=github.com&utm_medium=referral&utm_content=CLIUtils/CLI11&utm_campaign=Badge_Grade +[hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html +[standard readme style]: https://github.com/RichardLitt/standard-readme diff --git a/packages/CLI11/docs/CLI11.svg b/packages/CLI11/docs/CLI11.svg new file mode 100644 index 0000000000000000000000000000000000000000..e12b99b6a0453ca8db132473a26d8014aa315dce --- /dev/null +++ b/packages/CLI11/docs/CLI11.svg @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="34.342606mm" + height="15.300875mm" + viewBox="0 0 34.342606 15.300875" + version="1.1" + id="svg8" + inkscape:version="0.92.2 (unknown)" + sodipodi:docname="CLI11.svg" + inkscape:export-filename="/data/CLI11_300.png" + inkscape:export-xdpi="222.62143" + inkscape:export-ydpi="222.62143"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="11.206433" + inkscape:cx="93.996945" + inkscape:cy="15.843961" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="2560" + inkscape:window-height="1347" + inkscape:window-x="0" + inkscape:window-y="1" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-53.018986,-23.9019)"> + <g + id="g4602" + transform="rotate(-0.28559572,70.190289,31.552338)"> + <path + inkscape:connector-curvature="0" + id="path3732" + transform="scale(0.26458333)" + d="m 233.33789,90.337891 -32.95117,28.619139 31.94726,28.91406 64.9004,0.29688 32.95117,-28.61914 -31.94727,-28.914064 z" + style="fill:#008080;stroke-width:0.54128456" /> + <path + inkscape:connector-curvature="0" + style="fill:none;stroke:#ffffff;stroke-width:0.3148967;stroke-miterlimit:4;stroke-dasharray:none" + d="m 62.120274,24.413292 -8.32335,7.065988 8.069765,7.138804 16.393615,0.0733 8.32335,-7.065988 -8.069768,-7.138806 z" + id="path3774" /> + </g> + <g + id="g4609" + transform="translate(-0.43472687)"> + <path + inkscape:transform-center-y="0.00020894337" + inkscape:transform-center-x="0.0229185" + inkscape:connector-curvature="0" + id="path3730" + d="m 60.964519,27.804182 c 0.886395,-0.348655 1.859691,-0.390207 2.74248,-0.111952 0.651274,0.210103 1.042699,0.454066 1.576252,0.972044 l -1.506657,0.592635 c -0.744252,-0.446473 -1.423964,-0.497745 -2.270962,-0.164583 -0.738662,0.290549 -1.26082,0.814436 -1.498695,1.510755 -0.203801,0.580557 -0.182185,1.300104 0.05025,1.891033 0.534609,1.359137 2.079298,2.048044 3.418738,1.521183 0.699266,-0.275052 1.11846,-0.713017 1.465328,-1.565931 l 1.585527,-0.623658 c -0.04824,1.554281 -1.023053,2.892949 -2.510224,3.47792 -2.127345,0.836779 -4.497206,-0.187252 -5.322363,-2.28505 -0.809661,-2.058401 0.211919,-4.404734 2.270322,-5.214396 z" + style="fill:#ffffff;stroke-width:0.14321487" /> + <path + inkscape:connector-curvature="0" + id="path3728" + transform="scale(0.26458333)" + d="m 253.49609,104.47266 h 5.48047 v 24.32031 h 9.03906 v 5.24023 h -14.51953 z" + style="fill:#ffffff;stroke-width:0.54128456" /> + <path + inkscape:connector-curvature="0" + id="path3726" + transform="scale(0.26458333)" + d="m 271.07422,104.47266 h 5.48047 v 29.56054 h -5.48047 z" + style="fill:#ffffff;stroke-width:0.54128456" /> + <path + inkscape:connector-curvature="0" + id="path3724" + transform="scale(0.26458333)" + d="m 294.68555,104.47266 v 29.56054 h -5.32032 v -24.56054 h -3.71875 z" + style="fill:#ffffff;stroke-width:0.54128456" /> + <path + inkscape:connector-curvature="0" + id="path3705" + transform="scale(0.26458333)" + d="m 305.76758,104.47266 v 29.56054 h -5.32031 v -24.56054 h -3.71875 z" + style="fill:#ffffff;stroke-width:0.54128456" /> + </g> + </g> +</svg> diff --git a/packages/CLI11/docs/CLI11_100.png b/packages/CLI11/docs/CLI11_100.png new file mode 100644 index 0000000000000000000000000000000000000000..d318c48a0df1a7e23025a38cf592d4ce97dbdc97 Binary files /dev/null and b/packages/CLI11/docs/CLI11_100.png differ diff --git a/packages/CLI11/docs/CLI11_300.png b/packages/CLI11/docs/CLI11_300.png new file mode 100644 index 0000000000000000000000000000000000000000..3dcb4250604cfe8ffb1e3959eef7780ccd6afad7 Binary files /dev/null and b/packages/CLI11/docs/CLI11_300.png differ diff --git a/packages/CLI11/docs/Doxyfile b/packages/CLI11/docs/Doxyfile index 0e2a8c75b89dfee49740639db146ac14a4837996..5363a26e1f607228ee15e48dcf8e7f87a8d4aada 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_CLI11_SUPPORT = NO +CPP_CLI_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 c4618ca47025a30bddbf4a86007abb1d820b5444..ef17d2c2f8351f1771161281f503ea7d65c0a1c1 100644 --- a/packages/CLI11/examples/CMakeLists.txt +++ b/packages/CLI11/examples/CMakeLists.txt @@ -14,6 +14,33 @@ function(add_cli_exe T) endif() endfunction() +option(CLI11_EXAMPLE_JSON OFF) +if(CLI11_EXAMPLE_JSON) + if(NOT EXISTS "${CLI11_SOURCE_DIR}/extern/json/single_include/nlohmann/json.hpp") + message(ERROR "You are missing the json package for CLI11_EXAMPLE_JSON. Please update your submodules (git submodule update --init)") + endif() + if(CMAKE_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) + message(WARNING "The json example requires GCC 4.9+ (requirement on json library)") + endif() + add_cli_exe(json json.cpp) + target_include_directories(json PUBLIC SYSTEM "${CLI11_SOURCE_DIR}/extern/json/single_include") + + add_test(NAME json_config_out COMMAND json --item 2) + set_property(TEST json_config_out PROPERTY PASS_REGULAR_EXPRESSION + "{" + "\"item\": \"2\"" + "\"simple\": false" + "}") + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/input.json" [=[{"item":3,"simple":false}]=]) + add_test(NAME json_config_in COMMAND json --config "${CMAKE_CURRENT_BINARY_DIR}/input.json") + set_property(TEST json_config_in PROPERTY PASS_REGULAR_EXPRESSION + "{" + "\"item\": \"3\"" + "\"simple\": false" + "}") +endif() + add_cli_exe(simple simple.cpp) add_test(NAME simple_basic COMMAND simple) add_test(NAME simple_all COMMAND simple -f filename.txt -c 12 --flag --flag -d 1.2) @@ -36,6 +63,20 @@ set_property(TEST subcommands_all PROPERTY PASS_REGULAR_EXPRESSION "Subcommand: start" "Subcommand: stop") +add_cli_exe(validators validators.cpp) +add_test(NAME validators_help COMMAND validators --help) +set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION + " -f,--file FILE File name" + " -v,--value INT in [3 - 6] Value in range") +add_test(NAME validators_file COMMAND validators --file nonex.xxx) +set_property(TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION + "--file: File does not exist: nonex.xxx" + "Run with --help for more information.") +add_test(NAME validators_plain COMMAND validators --value 9) +set_property(TEST validators_plain PROPERTY PASS_REGULAR_EXPRESSION + "--value: Value 9 not in range 3 to 6" + "Run with --help for more information.") + add_cli_exe(groups groups.cpp) add_test(NAME groups_none COMMAND groups) set_property(TEST groups_none PROPERTY PASS_REGULAR_EXPRESSION @@ -85,3 +126,5 @@ set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION "Using foo!") add_cli_exe(formatter formatter.cpp) + +add_cli_exe(nested nested.cpp) diff --git a/packages/CLI11/examples/enum.cpp b/packages/CLI11/examples/enum.cpp index 3e841da6a70b46ddff5a64c382fafdefb8b8344f..5d404579ab31b4a89a63c71de8e789272a007f15 100644 --- a/packages/CLI11/examples/enum.cpp +++ b/packages/CLI11/examples/enum.cpp @@ -17,7 +17,7 @@ int main(int argc, char **argv) { Level level; 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}"); + ->type_name("enum/Level in {High=0, Medium=1, Low=2}"); CLI11_PARSE(app, argc, argv); diff --git a/packages/CLI11/examples/json.cpp b/packages/CLI11/examples/json.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bce5ecdabfa22d5695c583997dda4144a3cb1aaf --- /dev/null +++ b/packages/CLI11/examples/json.cpp @@ -0,0 +1,113 @@ +#include <CLI/CLI.hpp> +#include <nlohmann/json.hpp> + +// This example is only built on GCC 7 on Travis due to mismatch in stdlib +// for clang (CLI11 is forgiving about mismatches, json.hpp is not) + +using nlohmann::json; + +class ConfigJSON : public CLI::Config { + public: + std::string to_config(const CLI::App *app, bool default_also, bool, std::string) const override { + + json j; + + for(const CLI::Option *opt : app->get_options({})) { + + // Only process option with a long-name and configurable + if(!opt->get_lnames().empty() && opt->get_configurable()) { + std::string name = opt->get_lnames()[0]; + + // Non-flags + if(opt->get_type_size() != 0) { + + // If the option was found on command line + if(opt->count() == 1) + j[name] = opt->results().at(0); + else if(opt->count() > 1) + j[name] = opt->results(); + + // If the option has a default and is requested by optional argument + else if(default_also && !opt->get_defaultval().empty()) + j[name] = opt->get_defaultval(); + + // Flag, one passed + } else if(opt->count() == 1) { + j[name] = true; + + // Flag, multiple passed + } else if(opt->count() > 1) { + j[name] = opt->count(); + + // Flag, not present + } else if(opt->count() == 0 && default_also) { + j[name] = false; + } + } + } + + for(const CLI::App *subcom : app->get_subcommands({})) + j[subcom->get_name()] = json(to_config(subcom, default_also, false, "")); + + return j.dump(4); + } + + std::vector<CLI::ConfigItem> from_config(std::istream &input) const override { + json j; + input >> j; + return _from_config(j); + } + + std::vector<CLI::ConfigItem> + _from_config(json j, std::string name = "", std::vector<std::string> prefix = {}) const { + std::vector<CLI::ConfigItem> results; + + if(j.is_object()) { + for(json::iterator item = j.begin(); item != j.end(); ++item) { + auto copy_prefix = prefix; + if(!name.empty()) + copy_prefix.push_back(name); + auto sub_results = _from_config(*item, item.key(), copy_prefix); + results.insert(results.end(), sub_results.begin(), sub_results.end()); + } + } else if(!name.empty()) { + results.emplace_back(); + CLI::ConfigItem &res = results.back(); + res.name = name; + res.parents = prefix; + if(j.is_boolean()) { + res.inputs = {j.get<bool>() ? "true" : "false"}; + } else if(j.is_number()) { + std::stringstream ss; + ss << j.get<double>(); + res.inputs = {ss.str()}; + } else if(j.is_string()) { + res.inputs = {j.get<std::string>()}; + } else if(j.is_array()) { + for(std::string ival : j) + res.inputs.push_back(ival); + } else { + throw CLI::ConversionError("Failed to convert " + name); + } + } else { + throw CLI::ConversionError("You must make all top level values objects in json!"); + } + + return results; + } +}; + +int main(int argc, char **argv) { + CLI::App app; + app.config_formatter(std::make_shared<ConfigJSON>()); + + int item; + + app.add_flag("--simple"); + app.add_option("--item", item); + app.set_config("--config"); + + CLI11_PARSE(app, argc, argv); + + std::cout << app.config_to_str(true, true) << std::endl; +} diff --git a/packages/CLI11/examples/nested.cpp b/packages/CLI11/examples/nested.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1714b8df56bf4018d2d409bec791467faf3f772c --- /dev/null +++ b/packages/CLI11/examples/nested.cpp @@ -0,0 +1,22 @@ +#include <CLI/CLI.hpp> +#include <string> + +int main(int argc, char **argv) { + + CLI::App app("Vision Application"); + app.set_help_all_flag("--help-all", "Expand all help"); + app.add_flag("--version", "Get version"); + + CLI::App *cameraApp = app.add_subcommand("camera", "Configure the app camera"); + cameraApp->require_subcommand(0, 1); // 0 (default) or 1 camera + + std::string mvcamera_config_file = "mvcamera_config.json"; + CLI::App *mvcameraApp = cameraApp->add_subcommand("mvcamera", "MatrixVision Camera Configuration"); + mvcameraApp->add_option("-c,--config", mvcamera_config_file, "Config filename", true)->check(CLI::ExistingFile); + + std::string mock_camera_path; + CLI::App *mockcameraApp = cameraApp->add_subcommand("mock", "Mock Camera Configuration"); + mockcameraApp->add_option("-p,--path", mock_camera_path, "Path")->required()->check(CLI::ExistingPath); + + CLI11_PARSE(app, argc, argv); +} diff --git a/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp b/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp index e347148fc82e3081cb308c4c2b375c732fa3e17a..affc6e849e3abb0bfb14d80c074b6a2806f3b5df 100644 --- a/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp +++ b/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp @@ -18,7 +18,7 @@ void setup_subcommand_a(CLI::App &app) { sub->add_flag("--with-foo", opt->with_foo, "Counter"); // Set the run function as callback to be called when this subcommand is issued. - sub->set_callback([opt]() { run_subcommand_a(*opt); }); + sub->callback([opt]() { run_subcommand_a(*opt); }); } /// The function that runs our code. diff --git a/packages/CLI11/examples/validators.cpp b/packages/CLI11/examples/validators.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d431ed223b87eb7cbf082943e8400afe76733a24 --- /dev/null +++ b/packages/CLI11/examples/validators.cpp @@ -0,0 +1,17 @@ +#include "CLI/CLI.hpp" + +int main(int argc, char **argv) { + + CLI::App app("Validator checker"); + + std::string file; + app.add_option("-f,--file,file", file, "File name")->check(CLI::ExistingFile); + + int count; + app.add_option("-v,--value", count, "Value in range")->check(CLI::Range(3, 6)); + CLI11_PARSE(app, argc, argv); + + std::cout << "Try printing help or failing the validator" << std::endl; + + return 0; +} diff --git a/packages/CLI11/extern/json b/packages/CLI11/extern/json new file mode 160000 index 0000000000000000000000000000000000000000..d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe --- /dev/null +++ b/packages/CLI11/extern/json @@ -0,0 +1 @@ +Subproject commit d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index c1d30ec48826604741ee8cb1ce820b81f0971265..eebab3bfa0f6809b85b514799150eb6d77bb726f 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -222,13 +222,13 @@ class App { /// it is not possible to overload on std::function (fixed in c++14 /// and backported to c++11 on newer compilers). Use capture by reference /// to get a pointer to App if needed. - App *set_callback(std::function<void()> callback) { + App *callback(std::function<void()> callback) { callback_ = callback; return this; } /// Set a name for the app (empty will use parser to set the name) - App *set_name(std::string name = "") { + App *name(std::string name = "") { name_ = name; return this; } @@ -331,7 +331,7 @@ class App { CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; Option *opt = add_option(name, fun, description, false); - opt->set_custom_option(detail::type_name<T>()); + opt->type_name(detail::type_name<T>()); return opt; } @@ -345,11 +345,11 @@ class App { CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; Option *opt = add_option(name, fun, description, defaulted); - opt->set_custom_option(detail::type_name<T>()); + opt->type_name(detail::type_name<T>()); if(defaulted) { std::stringstream out; out << variable; - opt->set_default_str(out.str()); + opt->default_str(out.str()); } return opt; } @@ -371,7 +371,7 @@ class App { }; Option *opt = add_option(name, fun, description, false); - opt->set_custom_option(detail::type_name<T>(), -1); + opt->type_name(detail::type_name<T>())->type_size(-1); return opt; } @@ -393,9 +393,9 @@ class App { }; Option *opt = add_option(name, fun, description, defaulted); - opt->set_custom_option(detail::type_name<T>(), -1); + opt->type_name(detail::type_name<T>())->type_size(-1); if(defaulted) - opt->set_default_str("[" + detail::join(variable) + "]"); + opt->default_str("[" + detail::join(variable) + "]"); return opt; } @@ -440,7 +440,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(name); - opt->set_custom_option("", 0); + opt->type_size(0); return opt; } @@ -460,7 +460,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(name); - opt->set_custom_option("", 0); + opt->type_size(0); return opt; } @@ -480,7 +480,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(name); - opt->set_custom_option("", 0); + opt->type_size(0); opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); return opt; } @@ -499,7 +499,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(name); - opt->set_custom_option("", 0); + opt->type_size(0); return opt; } @@ -530,7 +530,7 @@ class App { Option *opt = add_option(name, fun, description, false); std::string typeval = detail::type_name<T>(); typeval += " in {" + detail::join(options) + "}"; - opt->set_custom_option(typeval); + opt->type_name(typeval); return opt; } @@ -550,7 +550,7 @@ class App { }; Option *opt = add_option(name, fun, description, false); - opt->set_type_name_fn( + opt->type_name_fn( [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); return opt; @@ -575,11 +575,11 @@ class App { Option *opt = add_option(name, fun, description, defaulted); std::string typeval = detail::type_name<T>(); typeval += " in {" + detail::join(options) + "}"; - opt->set_custom_option(typeval); + opt->type_name(typeval); if(defaulted) { std::stringstream out; out << member; - opt->set_default_str(out.str()); + opt->default_str(out.str()); } return opt; } @@ -601,12 +601,12 @@ class App { }; Option *opt = add_option(name, fun, description, defaulted); - opt->set_type_name_fn( + opt->type_name_fn( [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); if(defaulted) { std::stringstream out; out << member; - opt->set_default_str(out.str()); + opt->default_str(out.str()); } return opt; } @@ -634,7 +634,7 @@ class App { Option *opt = add_option(name, fun, description, false); std::string typeval = detail::type_name<std::string>(); typeval += " in {" + detail::join(options) + "}"; - opt->set_custom_option(typeval); + opt->type_name(typeval); return opt; } @@ -660,7 +660,7 @@ class App { }; Option *opt = add_option(name, fun, description, false); - opt->set_type_name_fn([&options]() { + opt->type_name_fn([&options]() { return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; }); @@ -691,9 +691,9 @@ class App { Option *opt = add_option(name, fun, description, defaulted); std::string typeval = detail::type_name<std::string>(); typeval += " in {" + detail::join(options) + "}"; - opt->set_custom_option(typeval); + opt->type_name(typeval); if(defaulted) { - opt->set_default_str(member); + opt->default_str(member); } return opt; } @@ -720,11 +720,11 @@ class App { }; Option *opt = add_option(name, fun, description, defaulted); - opt->set_type_name_fn([&options]() { + opt->type_name_fn([&options]() { return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; }); if(defaulted) { - opt->set_default_str(member); + opt->default_str(member); } return opt; } @@ -749,11 +749,11 @@ class App { }; CLI::Option *opt = add_option(name, fun, description, defaulted); - opt->set_custom_option(label, 2); + opt->type_name(label)->type_size(2); if(defaulted) { std::stringstream out; out << variable; - opt->set_default_str(out.str()); + opt->default_str(out.str()); } return opt; } @@ -878,6 +878,21 @@ class App { ///@} /// @name Parsing ///@{ + // + /// Reset the parsed data + void clear() { + + parsed_ = false; + missing_.clear(); + parsed_subcommands_.clear(); + + for(const Option_p &opt : options_) { + opt->clear(); + } + for(const App_p &app : subcommands_) { + app->clear(); + } + } /// Parses the command line - throws errors /// This must be called after the options are in but before the rest of the program. @@ -895,13 +910,22 @@ class App { /// The real work is done here. Expects a reversed vector. /// Changes the vector to the remaining options. void parse(std::vector<std::string> &args) { + // Clear if parsed + if(parsed_) + clear(); + + // Redundant (set by _parse on commands/subcommands) + // but placed here to make sure this is cleared when + // running parse after an error is thrown, even by _validate. + parsed_ = true; + _validate(); _parse(args); run_callback(); } /// Provide a function to print a help message. The function gets access to the App pointer and error. - void set_failure_message(std::function<std::string(const App *, const Error &e)> function) { + void failure_message(std::function<std::string(const App *, const Error &e)> function) { failure_message_ = function; } @@ -930,21 +954,6 @@ class App { return e.get_exit_code(); } - /// Reset the parsed data - void reset() { - - parsed_ = false; - missing_.clear(); - parsed_subcommands_.clear(); - - for(const Option_p &opt : options_) { - opt->clear(); - } - for(const App_p &app : subcommands_) { - app->reset(); - } - } - ///@} /// @name Post parsing ///@{ @@ -1012,7 +1021,7 @@ class App { ///@{ /// Set footer. - App *set_footer(std::string footer) { + App *footer(std::string footer) { footer_ = footer; return this; } @@ -1034,11 +1043,23 @@ class App { // Delegate to subcommand if needed auto selected_subcommands = get_subcommands(); if(!selected_subcommands.empty()) - return selected_subcommands.at(0)->help(prev); + return selected_subcommands.at(0)->help(prev, mode); else return formatter_->make_help(this, prev, mode); } + /// Provided for backwards compatibility \deprecated + CLI11_DEPRECATED("Please use footer instead") + App *set_footer(std::string msg) { return footer(msg); } + + /// Provided for backwards compatibility \deprecated + CLI11_DEPRECATED("Please use name instead") + App *set_name(std::string msg) { return name(msg); } + + /// Provided for backwards compatibility \deprecated + CLI11_DEPRECATED("Please use callback instead") + App *set_callback(std::function<void()> fn) { return callback(fn); } + ///@} /// @name Getters ///@{ @@ -1187,7 +1208,7 @@ class App { /// This returns the number of remaining options, minus the -- seperator size_t remaining_size(bool recurse = false) const { - size_t count = static_cast<size_t>(std::count_if( + auto 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; })); @@ -1349,10 +1370,13 @@ class App { size_t num_left_over = remaining_size(); if(num_left_over > 0) { args = remaining(false); - std::reverse(std::begin(args), std::end(args)); throw ExtrasError(args); } } + + if(parent_ == nullptr) { + args = remaining(false); + } } /// Parse one config param, return false if not found in any subcommand, remove if it is @@ -1608,6 +1632,7 @@ class App { namespace FailureMessage { +/// Printout a clean, simple message on error (the default in CLI11 1.5+) inline std::string simple(const App *app, const Error &e) { std::string header = std::string(e.what()) + "\n"; if(app->get_help_ptr() != nullptr) @@ -1615,6 +1640,7 @@ inline std::string simple(const App *app, const Error &e) { return header; } +/// Printout the full help string on error (if this fn is set, the old default for CLI11) inline std::string help(const App *app, const Error &e) { std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n"; header += app->help(); diff --git a/packages/CLI11/include/CLI/Config.hpp b/packages/CLI11/include/CLI/Config.hpp index 33f8619739745acba19dcdee5200621dc4a0e863..4a72a6198e60444d250bcc7f49dc869754962b8b 100644 --- a/packages/CLI11/include/CLI/Config.hpp +++ b/packages/CLI11/include/CLI/Config.hpp @@ -29,7 +29,7 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description, // If the option was found on command line if(opt->count() > 0) - value = detail::inijoin(opt->results()); + value = detail::ini_join(opt->results()); // If the option has a default and is requested by optional argument else if(default_also && !opt->get_defaultval().empty()) diff --git a/packages/CLI11/include/CLI/ConfigFwd.hpp b/packages/CLI11/include/CLI/ConfigFwd.hpp index 5ee2e1cbe6a402944f8c0aa8eebb93353572ff03..53be440368f8f5a0d75f71ca11890db158bc6289 100644 --- a/packages/CLI11/include/CLI/ConfigFwd.hpp +++ b/packages/CLI11/include/CLI/ConfigFwd.hpp @@ -16,7 +16,8 @@ class App; namespace detail { -inline std::string inijoin(std::vector<std::string> args) { +/// Comma separated join, adds quotes if needed +inline std::string ini_join(std::vector<std::string> args) { std::ostringstream s; size_t start = 0; for(const auto &arg : args) { @@ -37,6 +38,7 @@ inline std::string inijoin(std::vector<std::string> args) { } // namespace detail +/// Holds values to load into Options struct ConfigItem { /// This is the list of parents std::vector<std::string> parents; @@ -68,27 +70,7 @@ class Config { virtual std::vector<ConfigItem> from_config(std::istream &) const = 0; /// Convert a flag to a bool - virtual std::vector<std::string> to_flag(const ConfigItem &) const = 0; - - /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure - std::vector<ConfigItem> from_file(const std::string &name) { - std::ifstream input{name}; - if(!input.good()) - throw FileError::Missing(name); - - return from_config(input); - } - - /// virtual destructor - virtual ~Config() = default; -}; - -/// This converter works with INI files -class ConfigINI : public Config { - public: - std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; - - std::vector<std::string> to_flag(const ConfigItem &item) const override { + virtual std::vector<std::string> to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { std::string val = item.inputs.at(0); val = detail::to_lower(val); @@ -110,6 +92,24 @@ class ConfigINI : public Config { } } + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector<ConfigItem> from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + std::vector<ConfigItem> from_config(std::istream &input) const override { std::string line; std::string section = "default"; @@ -117,7 +117,7 @@ class ConfigINI : public Config { std::vector<ConfigItem> output; while(getline(input, line)) { - std::vector<std::string> items; + std::vector<std::string> items_buffer; detail::trim(line); size_t len = line.length(); @@ -132,10 +132,10 @@ class ConfigINI : public Config { if(pos != std::string::npos) { out.name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); - items = detail::split_up(item); + items_buffer = detail::split_up(item); } else { out.name = detail::trim_copy(line); - items = {"ON"}; + items_buffer = {"ON"}; } if(detail::to_lower(section) != "default") { @@ -149,7 +149,7 @@ class ConfigINI : public Config { out.parents.insert(out.parents.end(), plist.begin(), plist.end()); } - out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); + out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); } } return output; diff --git a/packages/CLI11/include/CLI/Formatter.hpp b/packages/CLI11/include/CLI/Formatter.hpp index 82e641a3b0bb0464e9faca925f8a9153c37f9985..7f1885ec9b42cd8cf6c5cb03f1543e429d8515b1 100644 --- a/packages/CLI11/include/CLI/Formatter.hpp +++ b/packages/CLI11/include/CLI/Formatter.hpp @@ -158,8 +158,7 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod out << make_subcommand(new_com); } else { out << new_com->help(new_com->get_name(), AppFormatMode::Sub); - if(new_com != subcommands_group.back()) - out << "\n"; + out << "\n"; } } } @@ -175,12 +174,19 @@ inline std::string Formatter::make_subcommand(const App *sub) const { inline std::string Formatter::make_expanded(const App *sub) const { std::stringstream out; - if(sub->get_description().empty()) - out << sub->get_name(); - else - out << sub->get_name() << " -> " << sub->get_description(); + out << sub->get_name() << "\n"; + + out << make_description(sub); + out << make_positionals(sub); out << make_groups(sub, AppFormatMode::Sub); - return out.str(); + out << make_subcommands(sub, AppFormatMode::Sub); + + // Drop blank spaces + std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n"); + tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n' + + // Indent all but the first line (the name) + return detail::find_and_replace(tmp, "\n", "\n ") + "\n"; } inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { diff --git a/packages/CLI11/include/CLI/FormatterFwd.hpp b/packages/CLI11/include/CLI/FormatterFwd.hpp index 92bfe65112f0d6331ffc59326ea24a119f6e6eca..0344256ca8c714b55f7d4b690530fde17277fba0 100644 --- a/packages/CLI11/include/CLI/FormatterFwd.hpp +++ b/packages/CLI11/include/CLI/FormatterFwd.hpp @@ -86,9 +86,11 @@ class FormatterBase { class FormatterLambda final : public FormatterBase { using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>; + /// The lambda to hold and run funct_t lambda_; public: + /// Create a FormatterLambda with a lambda function explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} /// This will simply call the lambda function @@ -97,6 +99,8 @@ class FormatterLambda final : public FormatterBase { } }; +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. class Formatter : public FormatterBase { public: Formatter() = default; diff --git a/packages/CLI11/include/CLI/Macros.hpp b/packages/CLI11/include/CLI/Macros.hpp index 6defe59ac20ad376cec7458362fa95116d0a1957..f7a266506d9df93cfe93a4fc3b0414fdc2589dad 100644 --- a/packages/CLI11/include/CLI/Macros.hpp +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -30,7 +30,7 @@ #endif #endif -#if defined(PYBIND11_CPP14) +#if defined(CLI11_CPP14) #define CLI11_DEPRECATED(reason) [[deprecated(reason)]] #elif defined(_MSC_VER) #define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index db625576eb8336d9733a5c4a211a4b305711b536..a5f046a22acf1a75aa7e406959dfd4feadedcbab 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -30,6 +30,8 @@ using Option_p = std::unique_ptr<Option>; enum class MultiOptionPolicy { Throw, TakeLast, TakeFirst, Join }; +/// This is the CRTP base class for Option and OptionDefaults. It was designed this way +/// to share parts of the class; an OptionDefaults can copy to an Option. template <typename CRTP> class OptionBase { friend App; @@ -49,6 +51,7 @@ template <typename CRTP> class OptionBase { /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; + /// Copy the contents to another similar class (one based on OptionBase) template <typename T> void copy_to(T *other) const { other->group(group_); other->required(required_); @@ -123,6 +126,8 @@ template <typename CRTP> class OptionBase { } }; +/// This is a version of OptionBase that only supports setting values, +/// for defaults. It is stored as the default option in an App. class OptionDefaults : public OptionBase<OptionDefaults> { public: OptionDefaults() = default; @@ -174,7 +179,7 @@ class Option : public OptionBase<Option> { /// A human readable type value, set when App creates this /// /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents. - std::function<std::string()> type_name_; + std::function<std::string()> type_name_{[]() { return std::string(); }}; /// True if this option has a default bool default_{false}; @@ -226,12 +231,10 @@ class Option : public OptionBase<Option> { ///@} /// Making an option by hand is not defined, it must be made by the App class - Option(std::string name, - std::string description = "", - std::function<bool(results_t)> callback = [](results_t) { return true; }, - bool default_ = true, - App *parent = nullptr) - : description_(std::move(description)), default_(default_), parent_(parent), callback_(std::move(callback)) { + Option( + std::string name, std::string description, std::function<bool(results_t)> callback, bool defaulted, App *parent) + : description_(std::move(description)), default_(defaulted), parent_(parent), + callback_(callback ? std::move(callback) : [](results_t) { return true; }) { std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(name)); } @@ -285,11 +288,11 @@ class Option : public OptionBase<Option> { Option *check(const Validator &validator) { validators_.emplace_back(validator.func); if(!validator.tname.empty()) - set_type_name(validator.tname); + type_name(validator.tname); return this; } - /// Adds a validator + /// Adds a validator. Takes a const string& and returns an error message (empty if conversion/check is okay). Option *check(std::function<std::string(const std::string &)> validator) { validators_.emplace_back(validator); return this; @@ -438,7 +441,7 @@ class Option : public OptionBase<Option> { /// 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) + /// \brief 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) @@ -543,7 +546,8 @@ class Option : public OptionBase<Option> { // 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())); + int trim_size = + std::min<int>(std::max<int>(std::abs(get_items_expected()), 1), static_cast<int>(results_.size())); // Operation depends on the policy setting if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { @@ -560,12 +564,18 @@ class Option : public OptionBase<Option> { local_result = !callback_(partial_result); } else { - // 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(get_name(), get_items_expected(), results_.size()); - else - local_result = !callback_(results_); + // Exact number required + if(get_items_expected() > 0) { + if(results_.size() != static_cast<size_t>(get_items_expected())) + throw ArgumentMismatch(get_name(), get_items_expected(), results_.size()); + // Variable length list + } else if(get_items_expected() < 0) { + // Require that this be a multiple of expected size and at least as many as expected + if(results_.size() < static_cast<size_t>(-get_items_expected()) || + results_.size() % static_cast<size_t>(std::abs(get_type_size())) != 0) + throw ArgumentMismatch(get_name(), get_items_expected(), results_.size()); + } + local_result = !callback_(results_); } if(local_result) @@ -630,15 +640,17 @@ class Option : public OptionBase<Option> { } /// Puts a result at the end - void add_result(std::string s) { + Option *add_result(std::string s) { results_.push_back(s); callback_run_ = false; + return this; } /// Set the results vector all at once - void set_results(std::vector<std::string> results) { + Option *set_results(std::vector<std::string> results) { results_ = results; callback_run_ = false; + return this; } /// Get a copy of the results @@ -651,36 +663,48 @@ class Option : public OptionBase<Option> { /// @name Custom options ///@{ - /// Set a custom option, typestring, type_size - void set_custom_option(std::string typeval, int type_size = 1) { - set_type_name(typeval); + /// Set the type function to run when displayed on this option + Option *type_name_fn(std::function<std::string()> typefun) { + type_name_ = typefun; + return this; + } + + /// Set a custom option typestring + Option *type_name(std::string typeval) { + type_name_fn([typeval]() { return typeval; }); + return this; + } + + /// Provided for backward compatibility \deprecated + CLI11_DEPRECATED("Please use type_name instead") + Option *set_type_name(std::string typeval) { return type_name(typeval); } + + /// Set a custom option size + Option *type_size(int type_size) { type_size_ = type_size; if(type_size_ == 0) required_ = false; if(type_size < 0) expected_ = -1; + return this; } /// Set the default value string representation - void set_default_str(std::string val) { defaultval_ = val; } + Option *default_str(std::string val) { + defaultval_ = val; + return this; + } /// Set the default value string representation and evaluate - void set_default_val(std::string val) { - set_default_str(val); + Option *default_val(std::string val) { + default_str(val); auto old_results = results_; results_ = {val}; run_callback(); results_ = std::move(old_results); + return this; } - /// Set the type name displayed on this option - void set_type_name(std::string typeval) { - set_type_name_fn([typeval]() { return typeval; }); - } - - /// Set the type function to run when displayed on this option - void set_type_name_fn(std::function<std::string()> typefun) { type_name_ = typefun; } - /// Get the typename for this option std::string get_type_name() const { return type_name_(); } }; diff --git a/packages/CLI11/include/CLI/Optional.hpp b/packages/CLI11/include/CLI/Optional.hpp index faaee3664c0ceee61d5d78b9bf19fe917e7de8f3..d4b7df28d52df89c45dc8d827911f818861dfea4 100644 --- a/packages/CLI11/include/CLI/Optional.hpp +++ b/packages/CLI11/include/CLI/Optional.hpp @@ -10,13 +10,16 @@ // [CLI11:verbatim] #ifdef __has_include +// You can explicitly enable or disable support +// by defining these to 1 or 0. #if defined(CLI11_CPP17) && __has_include(<optional>) && \ !defined(CLI11_STD_OPTIONAL) #define CLI11_STD_OPTIONAL 1 #endif #if defined(CLI11_CPP14) && __has_include(<experimental/optional>) && \ - !defined(CLI11_EXPERIMENTAL_OPTIONAL) + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) #define CLI11_EXPERIMENTAL_OPTIONAL 1 #endif diff --git a/packages/CLI11/include/CLI/StringTools.hpp b/packages/CLI11/include/CLI/StringTools.hpp index 24e01bcd7190ca3393fbc148e964b8e280f3d3dc..1b497837d1c1e21bf3c121af2017c1259e54cf10 100644 --- a/packages/CLI11/include/CLI/StringTools.hpp +++ b/packages/CLI11/include/CLI/StringTools.hpp @@ -204,5 +204,18 @@ inline std::string fix_newlines(std::string leader, std::string input) { return input; } +/// Find and replace a subtring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + } // namespace detail } // namespace CLI diff --git a/packages/CLI11/include/CLI/Timer.hpp b/packages/CLI11/include/CLI/Timer.hpp index eb6054ccb0868529961d6c0221fb9501d37c82dc..62ec84f0e6d9c3923cda011d2ccec7d3aeb2b7b3 100644 --- a/packages/CLI11/include/CLI/Timer.hpp +++ b/packages/CLI11/include/CLI/Timer.hpp @@ -17,6 +17,7 @@ namespace CLI { +/// This is a simple timer with pretty printing. Creating the timer starts counting. class Timer { protected: /// This is a typedef to make clocks easier to use @@ -82,6 +83,7 @@ class Timer { } // LCOV_EXCL_START + /// This prints out a time string from a time std::string make_time_str(double time) const { auto print_it = [](double x, std::string unit) { char buffer[50]; diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index 9af8ccaa62d264482599ad14788033dfe17b8ecf..d7c3d306ffad0e810bbfcdc1cbe0b2558a854562 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -12,18 +12,24 @@ namespace CLI { // Type tools -// We could check to see if C++14 is being used, but it does not hurt to redefine this -// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) -// It is not in the std namespace anyway, so no harm done. +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; +/// Check to see if something is a vector (fail check by default) template <typename T> struct is_vector { static const bool value = false; }; +/// Check to see if something is a vector (true if actually a vector) template <class T, class A> struct is_vector<std::vector<T, A>> { static bool const value = true; }; +/// Check to see if something is bool (fail check by default) template <typename T> struct is_bool { static const bool value = false; }; +/// Check to see if something is bool (true if actually a bool) template <> struct is_bool<bool> { static bool const value = true; }; namespace detail { diff --git a/packages/CLI11/include/CLI/Validators.hpp b/packages/CLI11/include/CLI/Validators.hpp index 6c41967e4d8a0075a86331350df994a0de93480d..91c5d9bc844b490b6194ab051cafb898d952fcaa 100644 --- a/packages/CLI11/include/CLI/Validators.hpp +++ b/packages/CLI11/include/CLI/Validators.hpp @@ -30,11 +30,14 @@ namespace CLI { struct Validator { /// This is the type name, if empty the type name will not be changed std::string tname; - std::function<std::string(const std::string &filename)> func; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function<std::string(const std::string &)> func; /// This is the required operator for a validator - provided to help - /// users (CLI11 uses the func directly) - std::string operator()(const std::string &filename) const { return func(filename); }; + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { return func(str); }; /// Combining validators is a new validator Validator operator&(const Validator &other) const { @@ -165,6 +168,10 @@ const detail::NonexistentPathValidator NonexistentPath; /// Produce a range (factory). Min and max are inclusive. struct Range : public Validator { + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). template <typename T> Range(T min, T max) { std::stringstream out; out << detail::type_name<T>() << " in [" << min << " - " << max << "]"; diff --git a/packages/CLI11/include/CLI/Version.hpp b/packages/CLI11/include/CLI/Version.hpp index 49393847c71d7d0d48580d290faa70ea6d00c003..80eaf2a0d2b674d03e4f222d83d2696657b4bb81 100644 --- a/packages/CLI11/include/CLI/Version.hpp +++ b/packages/CLI11/include/CLI/Version.hpp @@ -6,8 +6,8 @@ // [CLI11:verbatim] #define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 5 -#define CLI11_VERSION_PATCH 4 -#define CLI11_VERSION "1.5.4" +#define CLI11_VERSION_MINOR 6 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.6.1" // [CLI11:verbatim] diff --git a/packages/CLI11/scripts/MakeSingleHeader.py b/packages/CLI11/scripts/MakeSingleHeader.py index bb83e8cdeaa7254ed2aa80b13c5fa8a11fb59a4d..ab16799d2321a30984dd454fe843d59c4c1c8276 100755 --- a/packages/CLI11/scripts/MakeSingleHeader.py +++ b/packages/CLI11/scripts/MakeSingleHeader.py @@ -4,11 +4,11 @@ from __future__ import print_function, unicode_literals import os import re -import argparse -import operator +from argparse import ArgumentParser +from operator import add from copy import copy -from subprocess import check_output, CalledProcessError from functools import reduce +from subprocess import Popen, PIPE includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE) includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE) @@ -44,7 +44,7 @@ class HeaderFile(object): # add self.verbatim if 'CLI11:verbatim' in inner: - self.verbatim = ["\n\n// Verbatim copy from {}:".format(inc)] + self.verbatim = ["\n\n// Verbatim copy from {0}:".format(inc)] self.verbatim += verbatim_all.findall(inner) inner = verbatim_all.sub("", inner) else: @@ -52,7 +52,9 @@ class HeaderFile(object): self.headers = set(includes_system.findall(inner)) - self.body = '\n// From {}:\n\n'.format(inc) + inner[inner.find('namespace'):] + self.body = '\n// From {0}:\n\n'.format(inc) + inner[inner.find('namespace'):] + + self.namespace = None def __add__(self, other): out = copy(self) @@ -69,8 +71,15 @@ class HeaderFile(object): def verbatim_str(self): return '\n'.join(self.verbatim) + def insert_namespace(self, namespace): + self.namespace = namespace + + def macro_replacement(self, before, after): + self.verbatim = [x.replace(before, after) for x in self.verbatim] + self.body = self.body.replace(before, after) + def __str__(self): - return '''\ + result = '''\ #pragma once // CLI11: Version {self.VERSION} @@ -87,17 +96,27 @@ class HeaderFile(object): // Standard combined includes: {self.header_str} -{self.verbatim_str} -{self.body} '''.format(self=self) + if self.namespace: + result += '\nnamespace ' + self.namespace + ' {\n\n' + result += '{self.verbatim_str}\n{self.body}\n'.format(self=self) + if self.namespace: + result += '} // namespace ' + self.namespace + '\n\n' + + return result -def MakeHeader(output, main_header, include_dir = '../include'): + +def MakeHeader(output, main_header, include_dir = '../include', namespace=None, macro=None): # Set tag if possible to class variable try: - HeaderFile.TAG = check_output(['git', 'describe', '--tags', '--always'], cwd=str(DIR)).decode("utf-8").strip() - except CalledProcessError: + proc = Popen(['git', 'describe', '--tags', '--always'], cwd=str(DIR), stdout=PIPE) + out, _ = proc.communicate() + except OSError: pass + else: + if proc.returncode == 0: + HeaderFile.TAG = out.decode("utf-8").strip() base_dir = os.path.abspath(os.path.join(DIR, include_dir)) main_header = os.path.join(base_dir, main_header) @@ -112,20 +131,30 @@ def MakeHeader(output, main_header, include_dir = '../include'): include_files = includes_local.findall(header) headers = [HeaderFile(base_dir, inc) for inc in include_files] - single_header = reduce(operator.add, headers) + single_header = reduce(add, headers) + + if macro is not None: + before = 'CLI11_' + print("Converting macros", before, "->", macro) + single_header.macro_replacement(before, macro) + + if namespace: + print("Adding namespace", namespace) + single_header.insert_namespace(namespace) with open(output, 'w') as f: f.write(str(single_header)) print("Created", output) - if __name__ == '__main__': - parser = argparse.ArgumentParser() + parser = ArgumentParser(usage='Convert source to single header include. Can optionally add namespace and search-replace replacements (for macros).') 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') + parser.add_argument("--include", default='../include', help="The include directory") + parser.add_argument("--namespace", help="Add an optional namespace") + parser.add_argument("--macro", help="Replaces CLI11_ with NEW_PREFIX_") args = parser.parse_args() - MakeHeader(args.output, args.main, args.include) + MakeHeader(args.output, args.main, args.include, args.namespace, args.macro) diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index 76537e14ea23858e47bd2f6813b88738dfdcb085..1c6a969f7f05d6c33bdd944fc3796257bb8b3d5f 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -152,13 +152,10 @@ TEST_F(TApp, BoolAndIntFlags) { EXPECT_EQ(1, iflag); EXPECT_EQ((unsigned int)1, uflag); - app.reset(); - args = {"-b", "-b"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(bflag); - app.reset(); bflag = false; args = {"-iiiuu"}; @@ -173,11 +170,9 @@ TEST_F(TApp, BoolOnlyFlag) { app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw); args = {"-b"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(bflag); - app.reset(); - args = {"-b", "-b"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -332,8 +327,6 @@ TEST_F(TApp, MissingValueNonRequiredOpt) { args = {"-c"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); - args = {"--count"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); } @@ -347,8 +340,6 @@ TEST_F(TApp, MissingValueMoreThan) { args = {"-v", "2"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); - args = {"--vals", "4"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); } @@ -363,8 +354,6 @@ TEST_F(TApp, NoMissingValueMoreThan) { 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})); @@ -419,7 +408,6 @@ TEST_F(TApp, RequiredOptsDouble) { EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); args = {"--str", "one", "two"}; run(); @@ -436,8 +424,6 @@ TEST_F(TApp, RequiredOptsDoubleShort) { EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); - args = {"-s", "one", "-s", "one", "-s", "one"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); @@ -451,19 +437,13 @@ TEST_F(TApp, RequiredOptsDoubleNeg) { EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); - args = {"-s", "one", "two", "-s", "three"}; - EXPECT_NO_THROW(run()); - + ASSERT_NO_THROW(run()); EXPECT_EQ(strs, std::vector<std::string>({"one", "two", "three"})); - app.reset(); args = {"-s", "one", "two"}; - - EXPECT_NO_THROW(run()); - + ASSERT_NO_THROW(run()); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); } @@ -483,7 +463,6 @@ TEST_F(TApp, PositionalNoSpace) { EXPECT_EQ(options.size(), (size_t)1); EXPECT_EQ(options.at(0), "Test"); - app.reset(); args = {"-OTest", "param1", "param2"}; run(); @@ -505,7 +484,6 @@ TEST_F(TApp, PositionalNoSpaceLong) { EXPECT_EQ(options.size(), (size_t)1); EXPECT_EQ(options.at(0), "Test"); - app.reset(); args = {"--option=Test", "param1", "param2"}; run(); @@ -521,24 +499,20 @@ TEST_F(TApp, RequiredOptsUnlimited) { args = {"--str"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); args = {"--str", "one", "--str", "two"}; run(); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); - app.reset(); args = {"--str", "one", "two"}; run(); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); // It's better to feed a hungry option than to feed allow_extras - app.reset(); app.allow_extras(); run(); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); EXPECT_EQ(app.remaining(), std::vector<std::string>({})); - app.reset(); app.allow_extras(false); std::vector<std::string> remain; app.add_option("positional", remain); @@ -546,14 +520,12 @@ TEST_F(TApp, RequiredOptsUnlimited) { EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); EXPECT_EQ(remain, std::vector<std::string>()); - app.reset(); args = {"--str", "one", "--", "two"}; run(); EXPECT_EQ(strs, std::vector<std::string>({"one"})); EXPECT_EQ(remain, std::vector<std::string>({"two"})); - app.reset(); args = {"one", "--str", "two"}; run(); @@ -569,24 +541,20 @@ TEST_F(TApp, RequiredOptsUnlimitedShort) { args = {"-s"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); args = {"-s", "one", "-s", "two"}; run(); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); - app.reset(); args = {"-s", "one", "two"}; run(); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); // It's better to feed a hungry option than to feed allow_extras - app.reset(); app.allow_extras(); run(); EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); EXPECT_EQ(app.remaining(), std::vector<std::string>({})); - app.reset(); app.allow_extras(false); std::vector<std::string> remain; app.add_option("positional", remain); @@ -594,14 +562,12 @@ TEST_F(TApp, RequiredOptsUnlimitedShort) { EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); EXPECT_EQ(remain, std::vector<std::string>()); - app.reset(); args = {"-s", "one", "--", "two"}; run(); EXPECT_EQ(strs, std::vector<std::string>({"one"})); EXPECT_EQ(remain, std::vector<std::string>({"two"})); - app.reset(); args = {"one", "-s", "two"}; run(); @@ -635,7 +601,6 @@ TEST_F(TApp, RequireOptPriority) { EXPECT_EQ(strs, std::vector<std::string>({"one"})); EXPECT_EQ(remain, std::vector<std::string>({"two", "three"})); - app.reset(); args = {"two", "three", "--str", "one", "four"}; run(); @@ -656,7 +621,6 @@ TEST_F(TApp, RequireOptPriorityShort) { EXPECT_EQ(strs, std::vector<std::string>({"one"})); EXPECT_EQ(remain, std::vector<std::string>({"two", "three"})); - app.reset(); args = {"two", "three", "-s", "one", "four"}; run(); @@ -690,16 +654,12 @@ TEST_F(TApp, RequiredFlags) { EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); - args = {"-a"}; EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); args = {"-b"}; EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); args = {"-a", "-b"}; run(); } @@ -715,12 +675,10 @@ TEST_F(TApp, CallbackFlags) { run(); EXPECT_EQ(value, (size_t)0); - app.reset(); args = {"-v"}; run(); EXPECT_EQ(value, (size_t)1); - app.reset(); args = {"-vv"}; run(); EXPECT_EQ(value, (size_t)2); @@ -740,12 +698,10 @@ TEST_F(TApp, CallbackFlagsAuto) { run(); EXPECT_EQ(value, (size_t)0); - app.reset(); args = {"-v"}; run(); EXPECT_EQ(value, (size_t)1); - app.reset(); args = {"-vv"}; run(); EXPECT_EQ(value, (size_t)2); @@ -782,8 +738,6 @@ TEST_F(TApp, ForcedPositional) { EXPECT_TRUE(one->count()); EXPECT_EQ(answers1, posit); - app.reset(); - args = {"--", "--one", "two", "three"}; std::vector<std::string> answers2 = {"--one", "two", "three"}; run(); @@ -818,8 +772,6 @@ TEST_F(TApp, BigPositional) { run(); EXPECT_EQ(args, vec); - app.reset(); - args = {"one", "two"}; run(); @@ -840,7 +792,7 @@ TEST_F(TApp, Reset) { EXPECT_EQ((size_t)1, app.count("-d")); EXPECT_DOUBLE_EQ(1.2, doub); - app.reset(); + app.clear(); EXPECT_EQ((size_t)0, app.count("--simple")); EXPECT_EQ((size_t)0, app.count("-d")); @@ -866,7 +818,7 @@ TEST_F(TApp, RemoveOption) { TEST_F(TApp, FileNotExists) { std::string myfile{"TestNonFileNotUsed.txt"}; - EXPECT_NO_THROW(CLI::NonexistentPath(myfile)); + ASSERT_NO_THROW(CLI::NonexistentPath(myfile)); std::string filename; app.add_option("--file", filename)->check(CLI::NonexistentPath); @@ -875,8 +827,6 @@ TEST_F(TApp, FileNotExists) { run(); EXPECT_EQ(myfile, filename); - app.reset(); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_THROW(run(), CLI::ValidationError); @@ -895,8 +845,6 @@ TEST_F(TApp, FileExists) { EXPECT_THROW(run(), CLI::ValidationError); - app.reset(); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); run(); @@ -916,8 +864,6 @@ TEST_F(TApp, InSet) { run(); EXPECT_EQ("two", choice); - app.reset(); - args = {"--quick", "four"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -929,15 +875,12 @@ TEST_F(TApp, InSetWithDefault) { run(); EXPECT_EQ("one", choice); - app.reset(); args = {"--quick", "two"}; run(); EXPECT_EQ("two", choice); - app.reset(); - args = {"--quick", "four"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -949,15 +892,12 @@ TEST_F(TApp, InCaselessSetWithDefault) { run(); EXPECT_EQ("one", choice); - app.reset(); args = {"--quick", "tWo"}; run(); EXPECT_EQ("two", choice); - app.reset(); - args = {"--quick", "four"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -972,8 +912,6 @@ TEST_F(TApp, InIntSet) { run(); EXPECT_EQ(2, choice); - app.reset(); - args = {"--quick", "4"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -986,8 +924,6 @@ TEST_F(TApp, FailSet) { args = {"--quick", "3", "--quick=2"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); - app.reset(); - args = {"--quick=hello"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -1002,7 +938,6 @@ TEST_F(TApp, FailLValueSet) { args = {"--quick=hello"}; EXPECT_THROW(run(), CLI::ConversionError); - app.reset(); args = {"--slow=hello"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -1016,21 +951,17 @@ TEST_F(TApp, InSetIgnoreCase) { run(); EXPECT_EQ("one", choice); - app.reset(); args = {"--quick", "two"}; run(); EXPECT_EQ("Two", choice); // Keeps caps from set - app.reset(); args = {"--quick", "ThrEE"}; run(); EXPECT_EQ("THREE", choice); // Keeps caps from set - app.reset(); args = {"--quick", "four"}; EXPECT_THROW(run(), CLI::ConversionError); - app.reset(); args = {"--quick=one", "--quick=two"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); } @@ -1073,7 +1004,6 @@ TEST_F(TApp, VectorUnlimString) { EXPECT_EQ((size_t)3, app.count("--string")); EXPECT_EQ(answer, strvec); - app.reset(); args = {"-s", "mystring", "mystring2", "mystring3"}; run(); EXPECT_EQ((size_t)3, app.count("--string")); @@ -1092,11 +1022,9 @@ TEST_F(TApp, VectorFancyOpts) { EXPECT_EQ((size_t)3, app.count("--string")); EXPECT_EQ(answer, strvec); - app.reset(); args = {"one", "two"}; EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); EXPECT_THROW(run(), CLI::ParseError); } @@ -1122,15 +1050,12 @@ TEST_F(TApp, NeedsFlags) { run(); - app.reset(); args = {"-s"}; run(); - app.reset(); args = {"-s", "--both"}; run(); - app.reset(); args = {"--both"}; EXPECT_THROW(run(), CLI::RequiresError); } @@ -1141,19 +1066,15 @@ TEST_F(TApp, ExcludesFlags) { run(); - app.reset(); args = {"-s"}; run(); - app.reset(); args = {"--nostr"}; run(); - app.reset(); args = {"--nostr", "-s"}; EXPECT_THROW(run(), CLI::ExcludesError); - app.reset(); args = {"--string", "--nostr"}; EXPECT_THROW(run(), CLI::ExcludesError); } @@ -1166,19 +1087,15 @@ TEST_F(TApp, ExcludesMixedFlags) { run(); - app.reset(); args = {"--no"}; run(); - app.reset(); args = {"--opt2"}; run(); - app.reset(); args = {"--no", "--opt1"}; EXPECT_THROW(run(), CLI::ExcludesError); - app.reset(); args = {"--no", "--opt2"}; EXPECT_THROW(run(), CLI::ExcludesError); } @@ -1191,27 +1108,21 @@ TEST_F(TApp, NeedsMultiFlags) { run(); - app.reset(); args = {"--opt1"}; run(); - app.reset(); args = {"--opt2"}; run(); - app.reset(); args = {"--optall"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--optall", "--opt1"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--optall", "--opt2", "--opt1"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--optall", "--opt1", "--opt2", "--opt3"}; run(); } @@ -1224,27 +1135,21 @@ TEST_F(TApp, NeedsMixedFlags) { run(); - app.reset(); args = {"--opt1"}; run(); - app.reset(); args = {"--opt2"}; run(); - app.reset(); args = {"--optall"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--optall", "--opt1"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--optall", "--opt2", "--opt1"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--optall", "--opt1", "--opt2", "--opt3"}; run(); } @@ -1256,31 +1161,24 @@ TEST_F(TApp, NeedsChainedFlags) { run(); - app.reset(); args = {"--opt1"}; run(); - app.reset(); args = {"--opt2"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--opt3"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--opt3", "--opt2"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--opt3", "--opt1"}; EXPECT_THROW(run(), CLI::RequiresError); - app.reset(); args = {"--opt2", "--opt1"}; run(); - app.reset(); args = {"--opt1", "--opt2", "--opt3"}; run(); } @@ -1297,11 +1195,9 @@ TEST_F(TApp, Env) { EXPECT_EQ(2, val); EXPECT_EQ((size_t)1, vopt->count()); - app.reset(); vopt->required(); run(); - app.reset(); unset_env("CLI11_TEST_ENV_TMP"); EXPECT_THROW(run(), CLI::RequiredError); } @@ -1313,19 +1209,15 @@ TEST_F(TApp, RangeInt) { args = {"--one=1"}; EXPECT_THROW(run(), CLI::ValidationError); - app.reset(); args = {"--one=7"}; EXPECT_THROW(run(), CLI::ValidationError); - app.reset(); args = {"--one=3"}; run(); - app.reset(); args = {"--one=5"}; run(); - app.reset(); args = {"--one=6"}; run(); } @@ -1339,19 +1231,15 @@ TEST_F(TApp, RangeDouble) { args = {"--one=1"}; EXPECT_THROW(run(), CLI::ValidationError); - app.reset(); args = {"--one=7"}; EXPECT_THROW(run(), CLI::ValidationError); - app.reset(); args = {"--one=3"}; run(); - app.reset(); args = {"--one=5"}; run(); - app.reset(); args = {"--one=6"}; run(); } @@ -1367,7 +1255,7 @@ TEST_F(TApp, AllowExtras) { args = {"-x", "-f"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(val); EXPECT_EQ(app.remaining(), std::vector<std::string>({"-x"})); } @@ -1377,9 +1265,8 @@ TEST_F(TApp, AllowExtrasOrder) { app.allow_extras(); args = {"-x", "-f"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_EQ(app.remaining(), std::vector<std::string>({"-x", "-f"})); - app.reset(); std::vector<std::string> left_over = app.remaining(); app.parse(left_over); @@ -1462,15 +1349,12 @@ TEST_F(TApp, ThrowingTransform) { auto m = app.add_option("-m,--mess", val); m->transform([](std::string) -> std::string { throw CLI::ValidationError("My Message"); }); - EXPECT_NO_THROW(run()); - app.reset(); + ASSERT_NO_THROW(run()); args = {"-mone"}; ASSERT_THROW(run(), CLI::ValidationError); - app.reset(); - try { run(); } catch(const CLI::ValidationError &e) { @@ -1504,7 +1388,7 @@ TEST_F(TApp, CustomDoubleOption) { custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; return true; }); - opt->set_custom_option("INT FLOAT", 2); + opt->type_name("INT FLOAT")->type_size(2); args = {"12", "1.5"}; @@ -1533,17 +1417,14 @@ TEST_F(TApp, AddRemoveSetItems) { items.erase("TYPE1"); items.erase("TYPE2"); - app.reset(); args = {"--type1", "TYPE6", "--type2", "TYPE7"}; run(); EXPECT_EQ(type1, "TYPE6"); EXPECT_EQ(type2, "TYPE7"); - app.reset(); args = {"--type1", "TYPE1"}; EXPECT_THROW(run(), CLI::ConversionError); - app.reset(); args = {"--type2", "TYPE2"}; EXPECT_THROW(run(), CLI::ConversionError); } @@ -1567,17 +1448,47 @@ TEST_F(TApp, AddRemoveSetItemsNoCase) { items.erase("TYPE1"); items.erase("TYPE2"); - app.reset(); args = {"--type1", "TyPE6", "--type2", "tYPE7"}; run(); EXPECT_EQ(type1, "TYPE6"); EXPECT_EQ(type2, "TYPE7"); - app.reset(); args = {"--type1", "TYPe1"}; EXPECT_THROW(run(), CLI::ConversionError); - app.reset(); args = {"--type2", "TYpE2"}; EXPECT_THROW(run(), CLI::ConversionError); } + +// #128 +TEST_F(TApp, RepeatingMultiArgumentOptions) { + std::vector<std::string> entries; + app.add_option("--entry", entries, "set a key and value")->type_name("KEY VALUE")->type_size(-2); + + args = {"--entry", "key1", "value1", "--entry", "key2", "value2"}; + ASSERT_NO_THROW(run()); + EXPECT_EQ(entries, std::vector<std::string>({"key1", "value1", "key2", "value2"})); + + args.pop_back(); + ASSERT_THROW(run(), CLI::ArgumentMismatch); +} + +// #122 +TEST_F(TApp, EmptyOptionEach) { + std::string q; + app.add_option("--each", {})->each([&q](std::string s) { q = s; }); + + args = {"--each", "that"}; + run(); + + EXPECT_EQ(q, "that"); +} + +// #122 +TEST_F(TApp, EmptyOptionFail) { + std::string q; + app.add_option("--each", {}); + + args = {"--each", "that"}; + run(); +} diff --git a/packages/CLI11/tests/CMakeLists.txt b/packages/CLI11/tests/CMakeLists.txt index 81d9c118746b6872bf7919a0ac685b4767c53842..848f1bae12134777925aae5742414ab125f76b89 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -3,10 +3,12 @@ if(NOT EXISTS "${CLI11_SOURCE_DIR}/extern/googletest/CMakeLists.txt") git submodule update --init") endif() +list(APPEND CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/cmake") + # If submodule is available, add sanitizers # Set SANITIZE_ADDRESS, SANITIZE_MEMORY, SANITIZE_THREAD or SANITIZE_UNDEFINED if(EXISTS "${CLI11_SOURCE_DIR}/extern/sanitizers/cmake/FindSanitizers.cmake") - set(CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/extern/sanitizers/cmake" ${CMAKE_MODULE_PATH}) + list(APPEND CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/extern/sanitizers/cmake") find_package(Sanitizers) if(SANITIZE_ADDRESS) message(STATUS "You might want to use \"${ASan_WRAPPER}\" to run your program") @@ -30,8 +32,13 @@ set(CLI11_TESTS FormatterTest NewParseTest OptionalTest + DeprecatedTest ) +if(WIN32) + list(APPEND CLI11_TESTS WindowsTest) +endif() + set(CLI11_MULTIONLY_TESTS TimerTest ) @@ -66,6 +73,13 @@ foreach(T ${CLI11_MULTIONLY_TESTS}) endforeach() +# Add -Wno-deprecated-declarations to DeprecatedTest +if(NOT MSVC) + target_compile_options(DeprecatedTest PRIVATE -Wno-deprecated-declarations) + if(TARGET DeprecatedTest_Single) + target_compile_options(DeprecatedTest_Single PRIVATE -Wno-deprecated-declarations) + endif() +endif() # Link test (build error if inlines missing) add_library(link_test_1 link_test_1.cpp) diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index 72c00bd3c8ccec192f67139222915b70593f9dcc..e7c75f836a472373de4ddffad87e26ddc89c4d4e 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -362,7 +362,7 @@ TEST_F(TApp, SubcommandDefaults) { app.prefix_command(); app.ignore_case(); app.fallthrough(); - app.set_footer("footy"); + app.footer("footy"); app.group("Stuff"); app.require_subcommand(2, 3); diff --git a/packages/CLI11/tests/DeprecatedTest.cpp b/packages/CLI11/tests/DeprecatedTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ba574365de0d38182bbb520a1c05627aa2d71a20 --- /dev/null +++ b/packages/CLI11/tests/DeprecatedTest.cpp @@ -0,0 +1,43 @@ +#ifdef CLI11_SINGLE_FILE +#include "CLI11.hpp" +#else +#include "CLI/CLI.hpp" +#endif + +#include "gtest/gtest.h" + +TEST(Deprecated, SetFooter) { + CLI::App app{"My prog"}; + + app.set_footer("My Footer"); + EXPECT_EQ("My Footer", app.get_footer()); +} + +TEST(Deprecated, SetName) { + CLI::App app{"My prog"}; + + app.set_name("My Name"); + EXPECT_EQ("My Name", app.get_name()); +} + +TEST(Deprecated, SetCallback) { + CLI::App app{"My prog"}; + + bool val; + app.set_callback([&val]() { val = true; }); + + std::vector<std::string> something; + app.parse(something); + + EXPECT_TRUE(val); +} + +TEST(Deprecated, SetTypeName) { + CLI::App app{"My prog"}; + + std::string val; + auto opt = app.add_option("--val", val); + opt->set_type_name("THAT"); + + EXPECT_EQ(opt->get_type_name(), "THAT"); +} diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index 17bee24c783d0d98ffa1cb128b0b70477c50355e..8825b08e04365de55e36228d633c4deeb0424d62 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -24,7 +24,7 @@ TEST(THelp, Basic) { TEST(THelp, Footer) { CLI::App app{"My prog"}; - app.set_footer("Report bugs to bugs@example.com"); + app.footer("Report bugs to bugs@example.com"); std::string help = app.help(); @@ -128,7 +128,7 @@ TEST(THelp, VectorOpts) { TEST(THelp, MultiPosOpts) { CLI::App app{"My prog"}; - app.set_name("program"); + app.name("program"); std::vector<int> x, y; app.add_option("quick", x, "Disc")->expected(2); app.add_option("vals", y, "Other"); @@ -222,8 +222,8 @@ TEST(THelp, ManualSetters) { int x = 1; CLI::Option *op1 = app.add_option("--op", x); - op1->set_default_str("12"); - op1->set_type_name("BIGGLES"); + op1->default_str("12"); + op1->type_name("BIGGLES"); EXPECT_EQ(x, 1); std::string help = app.help(); @@ -231,7 +231,7 @@ TEST(THelp, ManualSetters) { EXPECT_THAT(help, HasSubstr("=12")); EXPECT_THAT(help, HasSubstr("BIGGLES")); - op1->set_default_val("14"); + op1->default_val("14"); EXPECT_EQ(x, 14); help = app.help(); EXPECT_THAT(help, HasSubstr("=14")); @@ -327,13 +327,11 @@ TEST(THelp, OnlyOneAllHelp) { std::vector<std::string> input{"--help-all"}; EXPECT_THROW(app.parse(input), CLI::ExtrasError); - app.reset(); std::vector<std::string> input2{"--yelp"}; EXPECT_THROW(app.parse(input2), CLI::CallForAllHelp); // Remove the flag app.set_help_all_flag(); - app.reset(); std::vector<std::string> input3{"--yelp"}; EXPECT_THROW(app.parse(input3), CLI::ExtrasError); } @@ -513,10 +511,11 @@ TEST_F(CapturedHelp, CallForAllHelpOutput) { " --help-all Help all\n" "\n" "Subcommands:\n" - "one -> One description\n" + "one\n" + " One description\n\n" "two\n" - "Options:\n" - " --three \n"); + " Options:\n" + " --three \n\n"); } TEST_F(CapturedHelp, NewFormattedHelp) { app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; }); @@ -535,7 +534,7 @@ TEST_F(CapturedHelp, NormalError) { } TEST_F(CapturedHelp, RepacedError) { - app.set_failure_message(CLI::FailureMessage::help); + app.failure_message(CLI::FailureMessage::help); EXPECT_EQ(run(CLI::ExtrasError({"Thing"})), static_cast<int>(CLI::ExitCodes::ExtrasError)); EXPECT_EQ(out.str(), ""); @@ -556,11 +555,19 @@ TEST(THelp, CustomDoubleOption) { custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; return true; }); - opt->set_custom_option("INT FLOAT", 2); + opt->type_name("INT FLOAT")->type_size(2); EXPECT_THAT(app.help(), Not(HasSubstr("x 2"))); } +TEST(THelp, CheckEmptyTypeName) { + CLI::App app; + + auto opt = app.add_flag("-f,--flag"); + std::string name = opt->get_type_name(); + EXPECT_TRUE(name.empty()); +} + TEST(THelp, AccessDescription) { CLI::App app{"My description goes here"}; diff --git a/packages/CLI11/tests/IniTest.cpp b/packages/CLI11/tests/IniTest.cpp index 92c4e35e58bbaf4d1122d95054bb9f251eaea91f..24d24ba48a15ec64b77daeccc09298ee0a1718e9 100644 --- a/packages/CLI11/tests/IniTest.cpp +++ b/packages/CLI11/tests/IniTest.cpp @@ -7,6 +7,13 @@ using ::testing::HasSubstr; using ::testing::Not; +TEST(StringBased, IniJoin) { + std::vector<std::string> items = {"one", "two", "three four"}; + std::string result = "one two \"three four\""; + + EXPECT_EQ(CLI::detail::ini_join(items), result); +} + TEST(StringBased, First) { std::stringstream ofile; @@ -186,7 +193,6 @@ TEST_F(TApp, IniNotRequired) { EXPECT_EQ(99, two); EXPECT_EQ(3, three); - app.reset(); one = two = three = 0; args = {"--one=1", "--two=2"}; @@ -231,7 +237,7 @@ TEST_F(TApp, IniGetRemainingOption) { int two = 0; app.add_option("--two", two); - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); std::vector<std::string> ExpectedRemaining = {ExtraOption}; EXPECT_EQ(app.remaining(), ExpectedRemaining); } @@ -249,7 +255,7 @@ TEST_F(TApp, IniGetNoRemaining) { int two = 0; app.add_option("--two", two); - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_EQ(app.remaining().size(), (size_t)0); } @@ -284,7 +290,6 @@ TEST_F(TApp, IniNotRequiredNotDefault) { EXPECT_EQ(99, two); EXPECT_EQ(3, three); - app.reset(); args = {"--config", tmpini2}; run(); @@ -353,18 +358,15 @@ TEST_F(TApp, IniRequired) { run(); - app.reset(); one = two = three = 0; args = {"--one=1", "--two=2"}; run(); - app.reset(); args = {}; EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); args = {"--two=2"}; EXPECT_THROW(run(), CLI::RequiredError); @@ -451,7 +453,7 @@ TEST_F(TApp, IniConfigurable) { out << "val=1" << std::endl; } - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(value); } @@ -531,7 +533,7 @@ TEST_F(TApp, IniFlagNumbers) { out << "flag=3" << std::endl; } - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(boo); } diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index a465618f46b525f5851997d57fa460567b8b57cc..68c6adfd77a3ac4e73574dd25c107d751027f089 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -17,11 +17,11 @@ add_option(CLI::App &app, std::string name, cx &variable, std::string descriptio }; CLI::Option *opt = app.add_option(name, fun, description, defaulted); - opt->set_custom_option("COMPLEX", 2); + opt->type_name("COMPLEX")->type_size(2); if(defaulted) { std::stringstream out; out << variable; - opt->set_default_str(out.str()); + opt->default_str(out.str()); } return opt; } diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp index 6c519e1f261a1254418e0a2f7e5f0f3216671267..5d68c72434b91c7f89eba4b772c65f8e17928e5b 100644 --- a/packages/CLI11/tests/OptionalTest.cpp +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -11,13 +11,11 @@ TEST_F(TApp, StdOptionalTest) { 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); @@ -33,13 +31,11 @@ TEST_F(TApp, ExperimentalOptionalTest) { 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); @@ -55,13 +51,11 @@ TEST_F(TApp, BoostOptionalTest) { 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); diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index 1a8174d942ae75cd5a9cbbf05e609c1c8610f337..bc5d8ca0095c0c311ce586107d1f2b90eef2fd7f 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -21,12 +21,12 @@ TEST_F(TApp, BasicSubcommands) { run(); EXPECT_EQ((size_t)0, app.get_subcommands().size()); - app.reset(); args = {"sub1"}; run(); EXPECT_EQ(sub1, app.get_subcommands().at(0)); + EXPECT_EQ((size_t)1, app.get_subcommands().size()); - app.reset(); + app.clear(); EXPECT_EQ((size_t)0, app.get_subcommands().size()); args = {"sub2"}; @@ -34,11 +34,9 @@ TEST_F(TApp, BasicSubcommands) { EXPECT_EQ((size_t)1, app.get_subcommands().size()); EXPECT_EQ(sub2, app.get_subcommands().at(0)); - app.reset(); args = {"SUb2"}; EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); args = {"SUb2"}; try { run(); @@ -46,7 +44,6 @@ TEST_F(TApp, BasicSubcommands) { EXPECT_THAT(e.what(), HasSubstr("SUb2")); } - app.reset(); args = {"sub1", "extra"}; try { run(); @@ -72,22 +69,18 @@ TEST_F(TApp, MultiSubFallthrough) { EXPECT_TRUE(app.got_subcommand(sub2)); EXPECT_TRUE(*sub2); - app.reset(); app.require_subcommand(); run(); - app.reset(); app.require_subcommand(2); run(); - app.reset(); app.require_subcommand(1); EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); args = {"sub1"}; run(); @@ -109,24 +102,21 @@ TEST_F(TApp, RequiredAndSubcoms) { // #23 auto bar = app.add_subcommand("bar"); args = {"bar", "foo"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(*foo); EXPECT_FALSE(*bar); EXPECT_EQ(baz, "bar"); - app.reset(); args = {"foo"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_FALSE(*foo); EXPECT_EQ(baz, "foo"); - app.reset(); args = {"foo", "foo"}; - EXPECT_NO_THROW(run()); + ASSERT_NO_THROW(run()); EXPECT_TRUE(*foo); EXPECT_EQ(baz, "foo"); - app.reset(); args = {"foo", "other"}; EXPECT_THROW(run(), CLI::ExtrasError); } @@ -144,7 +134,6 @@ TEST_F(TApp, RequiredAndSubcomFallthrough) { EXPECT_TRUE(bar); EXPECT_EQ(baz, "other"); - app.reset(); args = {"bar", "other2"}; EXPECT_THROW(run(), CLI::ExtrasError); } @@ -164,7 +153,6 @@ TEST_F(TApp, FooFooProblem) { EXPECT_EQ(baz_str, ""); EXPECT_EQ(other_str, "foo"); - app.reset(); baz_str = ""; other_str = ""; baz->required(); @@ -178,10 +166,10 @@ TEST_F(TApp, FooFooProblem) { TEST_F(TApp, Callbacks) { auto sub1 = app.add_subcommand("sub1"); - sub1->set_callback([]() { throw CLI::Success(); }); + sub1->callback([]() { throw CLI::Success(); }); auto sub2 = app.add_subcommand("sub2"); bool val = false; - sub2->set_callback([&val]() { val = true; }); + sub2->callback([&val]() { val = true; }); args = {"sub2"}; EXPECT_FALSE(val); @@ -191,14 +179,13 @@ TEST_F(TApp, Callbacks) { TEST_F(TApp, RuntimeErrorInCallback) { auto sub1 = app.add_subcommand("sub1"); - sub1->set_callback([]() { throw CLI::RuntimeError(); }); + sub1->callback([]() { throw CLI::RuntimeError(); }); auto sub2 = app.add_subcommand("sub2"); - sub2->set_callback([]() { throw CLI::RuntimeError(2); }); + sub2->callback([]() { throw CLI::RuntimeError(2); }); args = {"sub1"}; EXPECT_THROW(run(), CLI::RuntimeError); - app.reset(); args = {"sub1"}; try { run(); @@ -206,11 +193,9 @@ TEST_F(TApp, RuntimeErrorInCallback) { EXPECT_EQ(1, e.get_exit_code()); } - app.reset(); args = {"sub2"}; EXPECT_THROW(run(), CLI::RuntimeError); - app.reset(); args = {"sub2"}; try { run(); @@ -309,14 +294,13 @@ TEST_F(TApp, CallbackOrdering) { app.add_option("--val", val); auto sub = app.add_subcommand("sub"); - sub->set_callback([&val, &sub_val]() { sub_val = val; }); + sub->callback([&val, &sub_val]() { sub_val = val; }); args = {"sub", "--val=2"}; run(); EXPECT_EQ(2, val); EXPECT_EQ(2, sub_val); - app.reset(); args = {"--val=2", "sub"}; run(); EXPECT_EQ(2, val); @@ -331,10 +315,7 @@ TEST_F(TApp, RequiredSubCom) { EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); - args = {"sub1"}; - run(); } @@ -347,22 +328,16 @@ TEST_F(TApp, SubComExtras) { EXPECT_EQ(app.remaining(), std::vector<std::string>({"extra"})); EXPECT_EQ(sub->remaining(), std::vector<std::string>()); - app.reset(); - args = {"extra1", "extra2", "sub"}; run(); EXPECT_EQ(app.remaining(), std::vector<std::string>({"extra1", "extra2"})); EXPECT_EQ(sub->remaining(), std::vector<std::string>()); - app.reset(); - args = {"sub", "extra1", "extra2"}; run(); EXPECT_EQ(app.remaining(), std::vector<std::string>()); EXPECT_EQ(sub->remaining(), std::vector<std::string>({"extra1", "extra2"})); - app.reset(); - args = {"extra1", "extra2", "sub", "extra3", "extra4"}; run(); EXPECT_EQ(app.remaining(), std::vector<std::string>({"extra1", "extra2"})); @@ -378,11 +353,9 @@ TEST_F(TApp, Required1SubCom) { EXPECT_THROW(run(), CLI::RequiredError); - app.reset(); args = {"sub1"}; run(); - app.reset(); args = {"sub1", "sub2"}; EXPECT_THROW(run(), CLI::ExtrasError); } @@ -523,15 +496,12 @@ TEST_F(SubcommandProgram, CaseCheck) { args = {"Start"}; EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); args = {"start"}; run(); - app.reset(); start->ignore_case(); run(); - app.reset(); args = {"Start"}; run(); } @@ -546,12 +516,12 @@ TEST_F(TApp, SubcomInheritCaseCheck) { EXPECT_EQ((size_t)2, app.get_subcommands({}).size()); EXPECT_EQ((size_t)1, app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size()); - app.reset(); args = {"SuB1"}; run(); EXPECT_EQ(sub1, app.get_subcommands().at(0)); + EXPECT_EQ((size_t)1, app.get_subcommands().size()); - app.reset(); + app.clear(); EXPECT_EQ((size_t)0, app.get_subcommands().size()); args = {"sUb2"}; @@ -573,12 +543,10 @@ TEST_F(SubcommandProgram, HelpOrder) { TEST_F(SubcommandProgram, Callbacks) { - start->set_callback([]() { throw CLI::Success(); }); + start->callback([]() { throw CLI::Success(); }); run(); - app.reset(); - args = {"start"}; EXPECT_THROW(run(), CLI::Success); @@ -606,15 +574,12 @@ TEST_F(SubcommandProgram, ExtrasErrors) { args = {"one", "two", "start", "three", "four"}; EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); args = {"start", "three", "four"}; EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); args = {"one", "two"}; EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); } TEST_F(SubcommandProgram, OrderedExtras) { @@ -622,7 +587,6 @@ TEST_F(SubcommandProgram, OrderedExtras) { app.allow_extras(); args = {"one", "two", "start", "three", "four"}; EXPECT_THROW(run(), CLI::ExtrasError); - app.reset(); start->allow_extras(); @@ -632,7 +596,6 @@ TEST_F(SubcommandProgram, OrderedExtras) { EXPECT_EQ(start->remaining(), std::vector<std::string>({"three", "four"})); EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "three", "four"})); - app.reset(); args = {"one", "two", "start", "three", "--", "four"}; run(); @@ -656,7 +619,6 @@ TEST_F(SubcommandProgram, MixedOrderExtras) { EXPECT_EQ(stop->remaining(), std::vector<std::string>({"five", "six"})); EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "three", "four", "five", "six"})); - app.reset(); args = {"one", "two", "stop", "three", "four", "start", "five", "six"}; run(); @@ -668,14 +630,13 @@ TEST_F(SubcommandProgram, MixedOrderExtras) { TEST_F(SubcommandProgram, CallbackOrder) { std::vector<int> callback_order; - start->set_callback([&callback_order]() { callback_order.push_back(1); }); - stop->set_callback([&callback_order]() { callback_order.push_back(2); }); + start->callback([&callback_order]() { callback_order.push_back(1); }); + stop->callback([&callback_order]() { callback_order.push_back(2); }); args = {"start", "stop"}; run(); EXPECT_EQ(callback_order, std::vector<int>({1, 2})); - app.reset(); callback_order.clear(); args = {"stop", "start"}; @@ -728,7 +689,6 @@ TEST_F(ManySubcommands, Required1Fuzzy) { run(); EXPECT_EQ(sub1->remaining(), vs_t({"sub2", "sub3"})); - app.reset(); app.require_subcommand(-1); run(); @@ -742,7 +702,6 @@ TEST_F(ManySubcommands, Required2Fuzzy) { EXPECT_EQ(sub2->remaining(), vs_t({"sub3"})); EXPECT_EQ(app.remaining(true), vs_t({"sub3"})); - app.reset(); app.require_subcommand(-2); run(); @@ -753,13 +712,11 @@ TEST_F(ManySubcommands, Unlimited) { run(); EXPECT_EQ(app.remaining(true), vs_t()); - app.reset(); app.require_subcommand(); run(); EXPECT_EQ(app.remaining(true), vs_t()); - app.reset(); app.require_subcommand(2, 0); // 2 or more run(); diff --git a/packages/CLI11/tests/WindowsTest.cpp b/packages/CLI11/tests/WindowsTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bae812668b43689b6bc62aa0d9100356e70d786a --- /dev/null +++ b/packages/CLI11/tests/WindowsTest.cpp @@ -0,0 +1,13 @@ +#include "app_helper.hpp" +#include <Windows.h> + +// This test verifies that CLI11 still works if +// Windows.h is included. #145 + +TEST_F(TApp, WindowsTestSimple) { + app.add_flag("-c,--count"); + args = {"-c"}; + run(); + EXPECT_EQ((size_t)1, app.count("-c")); + EXPECT_EQ((size_t)1, app.count("--count")); +} diff --git a/packages/CLI11/tests/link_test_2.cpp b/packages/CLI11/tests/link_test_2.cpp index a198b6ed71d95c77621c474c34f16068c4ea268e..85c5a74fc6620da7410bd05d414365e8718d60d4 100644 --- a/packages/CLI11/tests/link_test_2.cpp +++ b/packages/CLI11/tests/link_test_2.cpp @@ -4,7 +4,7 @@ int do_nothing(); -// Verifies there are no ungarded inlines +// Verifies there are no unguarded inlines TEST(Link, DoNothing) { int a = do_nothing(); EXPECT_EQ(7, a);