From 50e0e2f202b234fb598bcf8a9e78eb0398f80686 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Mon, 10 Aug 2020 02:23:13 -0700 Subject: [PATCH] Add cxxopts module --- CMakeLists.txt | 12 + README.md | 3 +- modules/antkeeper-source | 2 +- modules/cxxopts/.gitignore | 8 + modules/cxxopts/.travis.yml | 70 + modules/cxxopts/CHANGELOG.md | 97 + modules/cxxopts/CMakeLists.txt | 108 + modules/cxxopts/INSTALL | 23 + modules/cxxopts/LICENSE | 19 + modules/cxxopts/README.md | 224 + modules/cxxopts/cxxopts-config.cmake.in | 4 + modules/cxxopts/include/cxxopts.hpp | 2231 ++++ modules/cxxopts/src/.gitignore | 1 + modules/cxxopts/src/CMakeLists.txt | 24 + modules/cxxopts/src/example.cpp | 168 + modules/cxxopts/test/.gitignore | 1 + modules/cxxopts/test/CMakeLists.txt | 35 + .../test/add-subdirectory-test/CMakeLists.txt | 11 + modules/cxxopts/test/catch.hpp | 10460 ++++++++++++++++ .../test/find-package-test/CMakeLists.txt | 11 + modules/cxxopts/test/link_a.cpp | 6 + modules/cxxopts/test/link_b.cpp | 1 + modules/cxxopts/test/main.cpp | 2 + modules/cxxopts/test/options.cpp | 776 ++ 24 files changed, 14295 insertions(+), 2 deletions(-) create mode 100644 modules/cxxopts/.gitignore create mode 100644 modules/cxxopts/.travis.yml create mode 100644 modules/cxxopts/CHANGELOG.md create mode 100644 modules/cxxopts/CMakeLists.txt create mode 100644 modules/cxxopts/INSTALL create mode 100644 modules/cxxopts/LICENSE create mode 100644 modules/cxxopts/README.md create mode 100644 modules/cxxopts/cxxopts-config.cmake.in create mode 100644 modules/cxxopts/include/cxxopts.hpp create mode 100644 modules/cxxopts/src/.gitignore create mode 100644 modules/cxxopts/src/CMakeLists.txt create mode 100644 modules/cxxopts/src/example.cpp create mode 100644 modules/cxxopts/test/.gitignore create mode 100644 modules/cxxopts/test/CMakeLists.txt create mode 100644 modules/cxxopts/test/add-subdirectory-test/CMakeLists.txt create mode 100644 modules/cxxopts/test/catch.hpp create mode 100644 modules/cxxopts/test/find-package-test/CMakeLists.txt create mode 100644 modules/cxxopts/test/link_a.cpp create mode 100644 modules/cxxopts/test/link_b.cpp create mode 100644 modules/cxxopts/test/main.cpp create mode 100644 modules/cxxopts/test/options.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5917542..db3c48c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,6 +178,18 @@ ExternalProject_Add(dirent "-DDIRENT_BUILD_TESTS=OFF" BUILD_ALWAYS 0) +# Build cxxopts module +ExternalProject_Add(cxxopts + SOURCE_DIR ${MODULE_SOURCE_DIR}/cxxopts + BINARY_DIR ${MODULE_BUILD_DIR}/cxxopts + INSTALL_DIR ${MODULE_INSTALL_DIR} + CMAKE_ARGS + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" + "-DCMAKE_INSTALL_PREFIX=${MODULE_INSTALL_DIR}" + "-DCXXOPTS_BUILD_EXAMPLES=OFF" + "-DCXXOPTS_BUILD_TESTS=OFF" + BUILD_ALWAYS 0) + # Build PhysicsFS module ExternalProject_Add(physfs SOURCE_DIR ${MODULE_SOURCE_DIR}/physfs diff --git a/README.md b/README.md index 2a2f009..4861ec1 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,10 @@ Antkeeper superbuild is licensed under the GNU General Public License, version 3 | Name | Author(s) | License | File(s) | | :------------------------------ | :--------------------- | :-------------------------- | :------------------------------------------------------------------ | +| cxxopts | Jarryd Beck | MIT License | [cxxopts.hpp](./modules/cxxopts/include/cxxopts.hpp) | | dirent | Toni Ronkko | MIT License | [dirent.h](./modules/dirent/include/dirent.h) | | dr_wav | David Reid | Public Domain (Unlicense) | [dr_wav.h](./modules/dr_wav/dr_wav.h) | -| Easing Functions (Equations) | Robert Penner | MIT License | [ease.hpp](./modules/antkeeper-source/src/animation/ease.hpp) | +| Easing Functions (Equations) | Robert Penner | MIT License | [ease.hpp](./modules/antkeeper-source/src/animation/ease.hpp) | | EnTT | Michele Caini | MIT License | [entt/*](./modules/entt/) | | khrplatform.h | The Khronos Group Inc. | MIT License | [khrplatform.h](./modules/glad/khrplatform.h) | | OpenGL loader generated by glad | David Herberth | Public Domain / WTFPL / CC0 | [glad.h](./modules/glad/glad.h), [glad.c](./modules/glad/glad.c) | diff --git a/modules/antkeeper-source b/modules/antkeeper-source index f44df4b..03dda20 160000 --- a/modules/antkeeper-source +++ b/modules/antkeeper-source @@ -1 +1 @@ -Subproject commit f44df4b7cc602875602e9eeecab28b9a58ead097 +Subproject commit 03dda20c2bfc481313ba446e34e4917dad0ec0bd diff --git a/modules/cxxopts/.gitignore b/modules/cxxopts/.gitignore new file mode 100644 index 0000000..e91cd40 --- /dev/null +++ b/modules/cxxopts/.gitignore @@ -0,0 +1,8 @@ +*.swp +build* +CMakeCache.txt +Makefile +CMakeFiles/ +Testing/ +CTestTestfile.cmake +cmake_install.cmake diff --git a/modules/cxxopts/.travis.yml b/modules/cxxopts/.travis.yml new file mode 100644 index 0000000..87c78a7 --- /dev/null +++ b/modules/cxxopts/.travis.yml @@ -0,0 +1,70 @@ +sudo: required +dist: trusty +language: cpp +os: + - linux +matrix: + include: + - os: linux + env: COMPILER=g++-4.9 + addons: + apt: + packages: + - g++-4.9 + sources: &sources + - llvm-toolchain-trusty-3.8 + - llvm-toolchain-trusty-5.0 + - ubuntu-toolchain-r-test + - os: linux + env: COMPILER=g++-4.9 UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes + addons: + apt: + packages: + - g++-4.9 + sources: *sources + - os: linux + env: COMPILER=g++-5 + addons: + apt: + packages: + - g++-5 + sources: *sources + - os: linux + env: COMPILER=g++-5 UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes + addons: + apt: + packages: + - g++-5 + sources: *sources + - os: linux + env: COMPILER=clang++-3.8 CXXFLAGS=-stdlib=libc++ + addons: + apt: + packages: + - clang-3.8 + - libc++-dev + sources: *sources + - os: linux + env: COMPILER=clang++-3.8 CXXFLAGS=-stdlib=libc++ UNICODE_OPTIONS=-DCXXOPTS_USE_UNICODE_HELP=Yes + addons: + apt: + packages: + - clang-3.8 + - libc++-dev + sources: *sources + - os: linux + env: COMPILER=clang++-5.0 CMAKE_OPTIONS=-DCXXOPTS_CXX_STANDARD=17 + addons: + apt: + packages: + - clang-5.0 + - g++-5 + sources: *sources +script: > + cmake -DCXXOPTS_BUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$COMPILER + -DCMAKE_CXX_FLAGS=$CXXFLAGS $UNICODE_OPTIONS $CMAKE_OPTIONS . + && make && make ARGS=--output-on-failure test + +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install icu4c; fi diff --git a/modules/cxxopts/CHANGELOG.md b/modules/cxxopts/CHANGELOG.md new file mode 100644 index 0000000..4bddcff --- /dev/null +++ b/modules/cxxopts/CHANGELOG.md @@ -0,0 +1,97 @@ +# Changelog + +This is the changelog for `cxxopts`, a C++11 library for parsing command line +options. The project adheres to semantic versioning. + +## Next version + +### Changed + +* Only search for a C++ compiler in CMakeLists.txt. +* Allow for exceptions to be disabled. +* Fix duplicate default options when there is a short and long option. +* Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions. +* Fix char parsing for space and check for length. + +## 2.2 + +### Changed + +* Allow integers to have leading zeroes. +* Build the tests by default. +* Don't check for container when showing positional help. + +### Added + +* Iterator inputs to `parse_positional`. +* Throw an exception if the option in `parse_positional` doesn't exist. +* Parse a delimited list in a single argument for vector options. +* Add an option to disable implicit value on booleans. + +### Bug Fixes + +* Fix a warning about possible loss of data. +* Fix version numbering in CMakeLists.txt +* Remove unused declaration of the undefined `ParseResult::get_option`. +* Throw on invalid option syntax when beginning with a `-`. +* Throw in `as` when option wasn't present. +* Fix catching exceptions by reference. +* Fix out of bounds errors parsing integers. + +## 2.1.1 + +### Bug Fixes + +* Revert the change adding `const` type for `argv`, because most users expect + to pass a non-const `argv` from `main`. + +## 2.1 + +### Changed + +* Options with implicit arguments now require the `--option=value` form if + they are to be specified with an option. This is to remove the ambiguity + when a positional argument could follow an option with an implicit value. + For example, `--foo value`, where `foo` has an implicit value, will be + parsed as `--foo=implicit` and a positional argument `value`. +* Boolean values are no longer special, but are just an option with a default + and implicit value. + +### Added + +* Added support for `std::optional` as a storage type. +* Allow the help string to be customised. +* Use `const` for the type in the `argv` parameter, since the contents of the + arguments is never modified. + +### Bug Fixes + +* Building against GCC 4.9 was broken due to overly strict shadow warnings. +* Fixed an ambiguous overload in the `parse_positional` function when an + `initializer_list` was directly passed. +* Fixed precedence in the Boolean value regex. + +## 2.0 + +### Changed + +* `Options::parse` returns a ParseResult rather than storing the parse + result internally. +* Options with default values now get counted as appearing once if they + were not specified by the user. + +### Added + +* A new `ParseResult` object that is the immutable result of parsing. It + responds to the same `count` and `operator[]` as `Options` of 1.x did. +* The function `ParseResult::arguments` returns a vector of the parsed + arguments to iterate through in the order they were provided. +* The symbol `cxxopts::version` for the version of the library. +* Booleans can be specified with various strings and explicitly set false. + +## 1.x + +The 1.x series was the first major version of the library, with release numbers +starting to follow semantic versioning, after 0.x being unstable. It never had +a changelog maintained for it. Releases mostly contained bug fixes, with the +occasional feature added. diff --git a/modules/cxxopts/CMakeLists.txt b/modules/cxxopts/CMakeLists.txt new file mode 100644 index 0000000..1b524f7 --- /dev/null +++ b/modules/cxxopts/CMakeLists.txt @@ -0,0 +1,108 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +cmake_minimum_required(VERSION 3.1) + +# parse the current version from the cxxopts header +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/cxxopts.hpp" cxxopts_version_defines + REGEX "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH)") +foreach(ver ${cxxopts_version_defines}) + if(ver MATCHES "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") + set(CXXOPTS__VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") + endif() +endforeach() +set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH}) +message(STATUS "cxxopts version ${VERSION}") + +project(cxxopts VERSION "${VERSION}" LANGUAGES CXX) + +enable_testing() + +option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ON) +option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ON) +option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ON) + +# request c++11 without gnu extension for the whole project and enable more warnings +if (CXXOPTS_CXX_STANDARD) + set(CMAKE_CXX_STANDARD ${CXXOPTS_CXX_STANDARD}) +else() + set(CMAKE_CXX_STANDARD 11) +endif() + +set(CMAKE_CXX_EXTENSIONS OFF) + +if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W2") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -Wshadow") +endif() + +add_library(cxxopts INTERFACE) +add_library(cxxopts::cxxopts ALIAS cxxopts) + +# optionally, enable unicode support using the ICU library +set(CXXOPTS_USE_UNICODE_HELP FALSE CACHE BOOL "Use ICU Unicode library") +if(CXXOPTS_USE_UNICODE_HELP) + find_package(PkgConfig) + pkg_check_modules(ICU REQUIRED icu-uc) + + target_link_libraries(cxxopts INTERFACE ${ICU_LDFLAGS}) + target_compile_options(cxxopts INTERFACE ${ICU_CFLAGS}) + target_compile_definitions(cxxopts INTERFACE CXXOPTS_USE_UNICODE) +endif() + +target_include_directories(cxxopts INTERFACE + $ + $ + ) + +if(CXXOPTS_ENABLE_INSTALL) + include(CMakePackageConfigHelpers) + set(CXXOPTS_CMAKE_DIR "lib/cmake/cxxopts" CACHE STRING + "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") + set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") + set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") + set(targets_export_name cxxopts-targets) + + # Generate the version, config and target files into the build directory. + write_basic_package_version_file( + ${version_config} + VERSION ${VERSION} + COMPATIBILITY AnyNewerVersion) + configure_package_config_file( + ${PROJECT_SOURCE_DIR}/cxxopts-config.cmake.in + ${project_config} + INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR}) + export(TARGETS cxxopts NAMESPACE cxxopts:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + + # Install version, config and target files. + install( + FILES ${project_config} ${version_config} + DESTINATION ${CXXOPTS_CMAKE_DIR}) + install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR} + NAMESPACE cxxopts::) + + # Install the header file and export the target + install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION lib) + install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION include) +endif() + +add_subdirectory(src) +add_subdirectory(test) diff --git a/modules/cxxopts/INSTALL b/modules/cxxopts/INSTALL new file mode 100644 index 0000000..3e1c6f3 --- /dev/null +++ b/modules/cxxopts/INSTALL @@ -0,0 +1,23 @@ +== System installation == + +This library is header only. So you can either copy `include/cxxopts.hpp` to `/usr/include` or `/usr/local/include`, or add `include` to your search path. + +== Building the examples and tests == + +It is preferable to build out of source. Make a build directory somewhere, and then +do the following, where `${CXXOPTS_DIR}` is the path that you checked out `cxxopts` +to: + + cmake ${CXXOPTS_DIR} + make + +You can use another build tool, such as ninja. + + cmake -G Ninja ${CXXOPTS_DIR} + ninja + + +To run the tests, you have to configure `cxxopts` with another flag: + cmake -D CXXOPTS_BUILD_TESTS=On ${CXXOPTS_DIR} + make + make test diff --git a/modules/cxxopts/LICENSE b/modules/cxxopts/LICENSE new file mode 100644 index 0000000..324a203 --- /dev/null +++ b/modules/cxxopts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/cxxopts/README.md b/modules/cxxopts/README.md new file mode 100644 index 0000000..88b92d4 --- /dev/null +++ b/modules/cxxopts/README.md @@ -0,0 +1,224 @@ +[![Build Status](https://travis-ci.org/jarro2783/cxxopts.svg?branch=master)](https://travis-ci.org/jarro2783/cxxopts) + +# Release versions + +Note that `master` is generally a work in progress, and you probably want to use a +tagged release version. + +# Quick start + +This is a lightweight C++ option parser library, supporting the standard GNU +style syntax for options. + +Options can be given as: + + --long + --long=argument + --long argument + -a + -ab + -abc argument + +where c takes an argument, but a and b do not. + +Additionally, anything after `--` will be parsed as a positional argument. + +## Basics + +```cpp +#include +``` + +Create a `cxxopts::Options` instance. + +```cpp +cxxopts::Options options("MyProgram", "One line description of MyProgram"); +``` + +Then use `add_options`. + +```cpp +options.add_options() + ("d,debug", "Enable debugging") // a bool parameter + ("i,integer", "Int param", cxxopts::value()) + ("f,file", "File name", cxxopts::value()) + ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")) + ; +``` + +Options are declared with a long and an optional short option. A description +must be provided. The third argument is the value, if omitted it is boolean. +Any type can be given as long as it can be parsed, with operator>>. + +To parse the command line do: + +```cpp +auto result = options.parse(argc, argv); +``` + +To retrieve an option use `result.count("option")` to get the number of times +it appeared, and + +```cpp +result["opt"].as() +``` + +to get its value. If "opt" doesn't exist, or isn't of the right type, then an +exception will be thrown. + +Note that the result of `options.parse` should only be used as long as the +`options` object that created it is in scope. + +## Exceptions + +Exceptional situations throw C++ exceptions. There are two types of +exceptions: errors defining the options, and errors when parsing a list of +arguments. All exceptions derive from `cxxopts::OptionException`. Errors +defining options derive from `cxxopts::OptionSpecException` and errors +parsing arguments derive from `cxxopts::OptionParseException`. + +All exceptions define a `what()` function to get a printable string +explaining the error. + +## Help groups + +Options can be placed into groups for the purposes of displaying help messages. +To place options in a group, pass the group as a string to `add_options`. Then, +when displaying the help, pass the groups that you would like displayed as a +vector to the `help` function. + +## Positional Arguments + +Positional arguments can be optionally parsed into one or more options. +To set up positional arguments, call + +```cpp +options.parse_positional({"first", "second", "last"}) +``` + +where "last" should be the name of an option with a container type, and the +others should have a single value. + +## Default and implicit values + +An option can be declared with a default or an implicit value, or both. + +A default value is the value that an option takes when it is not specified +on the command line. The following specifies a default value for an option: + +```cpp +cxxopts::value()->default_value("value") +``` + +An implicit value is the value that an option takes when it is given on the +command line without an argument. The following specifies an implicit value: + +```cpp +cxxopts::value()->implicit_value("implicit") +``` + +If an option had both, then not specifying it would give the value `"value"`, +writing it on the command line as `--option` would give the value `"implicit"`, +and writing `--option=another` would give it the value `"another"`. + +Note that the default and implicit value is always stored as a string, +regardless of the type that you want to store it in. It will be parsed as +though it was given on the command line. + +## Boolean values + +Boolean options have a default implicit value of `"true"`, which can be +overridden. The effect is that writing `-o` by itself will set option `o` to +`true`. However, they can also be written with various strings using `=value`. +There is no way to disambiguate positional arguments from the value following +a boolean, so we have chosen that they will be positional arguments, and +therefore, `-o false` does not work. + +## `std::vector` values + +Parsing of list of values in form of an `std::vector` is also supported, as long as `T` +can be parsed. To separate single values in a list the definition `CXXOPTS_VECTOR_DELIMITER` +is used, which is ',' by default. Ensure that you use no whitespaces between values because +those would be interpreted as the next command line option. Example for a command line option +that can be parsed as a `std::vector`: + +~~~ +--my_list=1,-2.1,3,4.5 +~~~ + +## Options specified multiple times + +The same option can be specified several times, with different arguments, which will all +be recorded in order of appearance. An example: + +~~~ +--use train --use bus --use ferry +~~~ + +this is supported through the use of a vector of value for the option: + +~~~ +options.add_options() + ("use", "Usable means of transport", cxxopts::value>()) +~~~ + +## Custom help + +The string after the program name on the first line of the help can be +completely replaced by calling `options.custom_help`. Note that you might +also want to override the positional help by calling `options.positional_help`. + + +## Example + +Putting all together: +```cpp +int main(int argc, char** argv) +{ + cxxopts::Options options("test", "A brief description"); + + options.add_options() + ("b,bar", "Param bar", cxxopts::value()) + ("d,debug", "Enable debugging", cxxopts::value()->default_value("false")) + ("f,foo", "Param foo", cxxopts::value()->default_value("10")) + ("h,help", "Print usage") + ; + + auto result = options.parse(argc, argv); + + if (result.count("help")) + { + std::cout << options.help() << std::endl; + exit(0); + } + bool debug = result["debug"].as(); + std::string bar; + if (result.count("bar")) + bar = result["bar"].as(); + int foo = result["foo"].as(); + + return 0; +} +``` + +# Linking + +This is a header only library. + +# Requirements + +The only build requirement is a C++ compiler that supports C++11 features such as: + +* regex +* constexpr +* default constructors + +GCC >= 4.9 or clang >= 3.1 with libc++ are known to work. + +The following compilers are known not to work: + +* MSVC 2013 + +# TODO list + +* Allow unrecognised options. diff --git a/modules/cxxopts/cxxopts-config.cmake.in b/modules/cxxopts/cxxopts-config.cmake.in new file mode 100644 index 0000000..c9efaf1 --- /dev/null +++ b/modules/cxxopts/cxxopts-config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) +check_required_components(cxxopts) diff --git a/modules/cxxopts/include/cxxopts.hpp b/modules/cxxopts/include/cxxopts.hpp new file mode 100644 index 0000000..ae95343 --- /dev/null +++ b/modules/cxxopts/include/cxxopts.hpp @@ -0,0 +1,2231 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 0 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, const String& a) + { + return s.append(a); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } // namespace + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + explicit OptionException(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + explicit OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + explicit OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + explicit option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + explicit invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + explicit option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + explicit option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + explicit missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + explicit option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + explicit option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class option_has_no_value_exception : public OptionException + { + public: + explicit option_has_no_value_exception(const std::string& option) + : OptionException( + option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + explicit argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + explicit option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + template + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif + } + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } // namespace + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } // namespace detail + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return static_cast(-static_cast(t-1)-1); + } + + template + T + checked_negate(T&& t, const std::string& text, std::false_type) + { + throw_or_mimic(text); + return t; + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw_or_mimic(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + inline + void parse_value(const std::string& text, char& c) + { + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; + } + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } // namespace values + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + std::string short_, + std::string long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_name = &details->long_name(); + } + + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_name = &details->long_name(); + m_value->parse(); + } + + CXXOPTS_NODISCARD + size_t + count() const noexcept + { + return m_count; + } + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_name == nullptr ? "" : *m_long_name); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + const std::string* m_long_name = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw_or_mimic(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(const std::string& a); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + struct Option + { + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; + }; + + class Options + { + using OptionMap = std::unordered_map>; + public: + + explicit Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list