diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eefb03f7b..34cf397b9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: windows: runs-on: windows-2019 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 @@ -56,9 +56,9 @@ jobs: ubuntu: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 @@ -67,9 +67,6 @@ jobs: - name: Configure CMake run: cmake . -G Ninja -B ${{ env.build_dir }} -DCMAKE_BUILD_TYPE=${{ env.config }} - env: - CC: gcc-9 - CXX: g++-9 - name: Build library sources run: cmake --build ${{ env.build_dir }} @@ -83,7 +80,7 @@ jobs: macos: runs-on: macos-14 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7596fae92..8c8cfa4f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,7 @@ if (SPLA_BUILD_OPENCL) add_subdirectory(deps/opencl-headers) message(STATUS "Add Khronos OpenCL ICD loaded library") + set(OPENCL_ICD_LOADER_PIC ON) set(OPENCL_ICD_LOADER_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(OPENCL_ICD_LOADER_BUILD_TESTING OFF CACHE BOOL "" FORCE) add_subdirectory(deps/opencl-icd-loader) diff --git a/deps/cxxopts/.github/workflows/cmake.yml b/deps/cxxopts/.github/workflows/cmake.yml index 3589fcb6f..2967916bb 100644 --- a/deps/cxxopts/.github/workflows/cmake.yml +++ b/deps/cxxopts/.github/workflows/cmake.yml @@ -2,43 +2,68 @@ name: CMake on: push: - branches: [ master ] + branches: [ master, main ] pull_request: - branches: [ master ] + branches: [ master, main ] workflow_dispatch: env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release + +defaults: + run: + shell: bash + jobs: - build: - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + build-ubuntu: + strategy: + matrix: + os: [ ubuntu-20.04, ubuntu-22.04 ] + compiler: [ g++-9, g++-10, clang++ ] + + name: Build and Test on Ubuntu runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v3 + - name: Configure CMake + run: cmake -S "${{github.workspace}}" -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=${{matrix.compiler}} + - name: Build + run: cmake --build "${{github.workspace}}/build" --config $BUILD_TYPE + - name: Test + working-directory: ${{github.workspace}}/build/test + run: ctest -C $BUILD_TYPE --output-on-failure + build-macos: + name: Build and Test on MacOS strategy: matrix: - os: [ubuntu-18.04] - compiler: [g++-7, g++-9, g++-10, clang++] - + os: [ macos-11, macos-12 ] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v3 + - name: Configure CMake + run: cmake -S "${{github.workspace}}" -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: Show compile commands + run: cat build/compile_commands.json + - name: Build + run: cmake --build "${{github.workspace}}/build" --config $BUILD_TYPE + - name: Test + working-directory: ${{github.workspace}}/build/test + shell: bash + run: ctest -C $BUILD_TYPE --output-on-failure + + build-windows: + name: Build and Test on Windows + runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=${{matrix.compiler}} - - - name: Build - # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - - name: Test - working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} - + - uses: actions/checkout@v3 + - uses: ilammy/msvc-dev-cmd@v1 + - name: Configure CMake + run: cmake -S "${{github.workspace}}" -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -T "ClangCl" + - name: Build + run: cmake --build "${{github.workspace}}/build" --config $BUILD_TYPE + - name: Test + working-directory: ${{github.workspace}}/build/test + run: cd $BUILD_TYPE && ./link_test && ./options_test diff --git a/deps/cxxopts/.gitignore b/deps/cxxopts/.gitignore index 4038fb17e..65d8d32c1 100644 --- a/deps/cxxopts/.gitignore +++ b/deps/cxxopts/.gitignore @@ -1,9 +1,77 @@ +syntax: glob + +# Temporary, cache, swap files +\#\#* *.swp -build*/ +*.bkp + +# Files which "ask" to be hidden +*~ +.* +unused/ + +# Build artifacts +*.a +*.o +*.so +*.ptx +bin/* +lib/* +build/ +build-*/ +bazel-* + +# Core dumps +core +core.* +core-* + +# CMake & CTest-generated files CMakeCache.txt -Makefile CMakeFiles/ -Testing/ -CTestTestfile.cmake cmake_install.cmake -bazel-* +CMakeScripts/* +CMakeTmp/* +Makefile +CTestTestfile.cmake +Testing/ + +# Eclise IDE-related files +.project +.cproject +.settings + +# CLion IDE-related files +.idea/ +cmake-build-*/ + +# Patching +*.diff +*.rej +*.orig + +# Files/folders downloaded from other repositories as part of the build +external/* +third-party/* + +# Miscellaneous +tags +log +*.log +*.v3breakpoints +gmon.out +.DS_Store + +# Doxygen +doxygen.log +Doxyfile +docs/ + +# Archives +*.zip +*.gz +*.bz2 +*.tgz +*.tar +*.xz + diff --git a/deps/cxxopts/BUILD.bazel b/deps/cxxopts/BUILD.bazel index a66fae7d8..a6cd02143 100644 --- a/deps/cxxopts/BUILD.bazel +++ b/deps/cxxopts/BUILD.bazel @@ -6,3 +6,11 @@ cc_library( strip_include_prefix = "include", visibility = ["//visibility:public"], ) + +load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test") + +cc_fuzz_test( + name = "cxxopts_fuzz_test", + srcs = ["test/fuzz.cpp"], + deps = [":cxxopts"], +) \ No newline at end of file diff --git a/deps/cxxopts/CHANGELOG.md b/deps/cxxopts/CHANGELOG.md index 220717d03..0c7648694 100644 --- a/deps/cxxopts/CHANGELOG.md +++ b/deps/cxxopts/CHANGELOG.md @@ -3,11 +3,55 @@ This is the changelog for `cxxopts`, a C++11 library for parsing command line options. The project adheres to semantic versioning. -## Unreleased +## 3.2.1 + +### Bug fixes + +* Fix compilation with optional on C++20. + +## 3.2 + +### Bug fixes + +* Fix unannotated fallthrough. +* Fix sign conversion with Unicode output. +* Don't initialize regex in static initialiser. +* Fix incorrect integer overflow checks. + +### Added + +* Add fuzzing to CI + +### Changed + +* Change quote output to '' to match Windows. +* Don't split positional arguments by the list delimiter. +* Order help groups by the order they were added. + +## 3.1.1 + +### Bug Fixes + +* Fixed version number in header. +* Fixed cast warning in Unicode function. + +## 3.1 ### Added +* Support for multiple long names for the same option (= multiple long aliases) * Add a `program()` function to retrieve the program name. +* Added a .clang-format file. +* Added iterator and printing for a ParseResult. + +### Changed + +* Cleanup exception code, add cxxopts::exceptions namespace. +* Renamed several exceptions to be more descriptive, and added to a nested namespace. + +### Bug Fixes + +* Fix `arguments()` having no key for options that only have a short name. ## 3.0 diff --git a/deps/cxxopts/CMakeLists.txt b/deps/cxxopts/CMakeLists.txt index 4c1a68606..110d3dd79 100644 --- a/deps/cxxopts/CMakeLists.txt +++ b/deps/cxxopts/CMakeLists.txt @@ -17,12 +17,10 @@ # 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...3.19) +cmake_minimum_required(VERSION 3.5...3.19) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") include(cxxopts) -set("PROJECT_DESCRIPTION" "A header-only lightweight C++ command line option parser") -set("PROJECT_HOMEPAGE_URL" "https://github.com/jarro2783/cxxopts") # Get the version of the library cxxopts_getversion(VERSION) @@ -32,6 +30,9 @@ project(cxxopts LANGUAGES CXX ) +set("PROJECT_DESCRIPTION" "A header-only lightweight C++ command line option parser") +set("PROJECT_HOMEPAGE_URL" "https://github.com/jarro2783/cxxopts") + # Must include after the project call due to GNUInstallDirs requiring a language be enabled (IE. CXX) include(GNUInstallDirs) diff --git a/deps/cxxopts/INSTALL b/deps/cxxopts/INSTALL index 3e1c6f3da..de7b0a813 100644 --- a/deps/cxxopts/INSTALL +++ b/deps/cxxopts/INSTALL @@ -21,3 +21,26 @@ To run the tests, you have to configure `cxxopts` with another flag: cmake -D CXXOPTS_BUILD_TESTS=On ${CXXOPTS_DIR} make make test + +== Using cxxopts in tipi.build projects == + +`cxxopts` can be easily used in [tipi.build](https://tipi.build) projects simply by adding the following entry to your `.tipi/deps`: + +```json +{ + "jarro2783/cxxopts": { "@": "v3.0.0" } +} +``` + +To try this you can run the following command in `/src` (change the target name appropriately to `linux` or `macos` or `windows`): + +```bash +tipi . -t +./build/linux-cxx17/bin/test_package -v +``` + +To develop `cxxopts` using tipi run the following command at the root of the repository: + +```bash +tipi . -t --test all -v +``` diff --git a/deps/cxxopts/README.md b/deps/cxxopts/README.md index ede30ef4d..603e4350c 100644 --- a/deps/cxxopts/README.md +++ b/deps/cxxopts/README.md @@ -85,9 +85,6 @@ 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. - ## Unrecognised arguments You can allow unrecognised arguments to be skipped. This applies to both @@ -109,9 +106,9 @@ result.unmatched() 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`. +arguments. All exceptions derive from `cxxopts::exceptions::exception`. Errors +defining options derive from `cxxopts::exceptions::specification` and errors +parsing arguments derive from `cxxopts::exceptions::parsing`. All exceptions define a `what()` function to get a printable string explaining the error. @@ -125,15 +122,37 @@ vector to the `help` function. ## Positional Arguments -Positional arguments can be optionally parsed into one or more options. -To set up positional arguments, call +Positional arguments are those given without a preceding flag and can be used +alongside non-positional arguments. There may be multiple positional arguments, +and the final positional argument may be a container type to hold a list of all +remaining positionals. + +To set up positional arguments, first declare the options, then configure a +set of those arguments as positional like: ```cpp -options.parse_positional({"first", "second", "last"}) +options.add_options() + ("script", "The script file to execute", cxxopts::value()) + ("server", "The server to execute on", cxxopts::value()) + ("filenames", "The filename(s) to process", cxxopts::value>()); + +options.parse_positional({"script", "server", "filenames"}); + +// Parse options the usual way +options.parse(argc, argv); ``` -where "last" should be the name of an option with a container type, and the -others should have a single value. +For example, parsing the following arguments: +~~~ +my_script.py my_server.com file1.txt file2.txt file3.txt +~~~ +will result in parsed arguments like the following table: + +| Field | Value | +| ------------- | ----------------------------------------- | +| `"script"` | `"my_script.py"` | +| `"server"` | `"my_server.com"` | +| `"filenames"` | `{"file1.txt", "file2.txt", "file3.txt"}` | ## Default and implicit values @@ -174,8 +193,8 @@ 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` +Parsing a list of values into a `std::vector` is also supported, as long as `T` +can be parsed. To separate single values in a list the define symbol `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`: @@ -256,7 +275,3 @@ 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/deps/cxxopts/WORKSPACE b/deps/cxxopts/WORKSPACE index e69de29bb..6a0fa2770 100644 --- a/deps/cxxopts/WORKSPACE +++ b/deps/cxxopts/WORKSPACE @@ -0,0 +1,16 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_fuzzing", + sha256 = "d9002dd3cd6437017f08593124fdd1b13b3473c7b929ceb0e60d317cb9346118", + strip_prefix = "rules_fuzzing-0.3.2", + urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"], +) + +load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies") + +rules_fuzzing_dependencies() + +load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init") + +rules_fuzzing_init() diff --git a/deps/cxxopts/cmake/cxxopts.cmake b/deps/cxxopts/cmake/cxxopts.cmake index 46e87ba60..b26c18a8e 100644 --- a/deps/cxxopts/cmake/cxxopts.cmake +++ b/deps/cxxopts/cmake/cxxopts.cmake @@ -87,18 +87,25 @@ endfunction() # Helper function to ecapsulate install logic function(cxxopts_install_logic) - string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + if(NOT CXXOPTS_USE_UNICODE_HELP) + if(CMAKE_LIBRARY_ARCHITECTURE) + string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + else() + # On some systems (e.g. NixOS), `CMAKE_LIBRARY_ARCHITECTURE` can be empty + set(CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_DATAROOTDIR}") + endif() + if(${CMAKE_VERSION} VERSION_GREATER "3.14") + set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") + endif() + else() + set(CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + endif() set(CXXOPTS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/cxxopts" CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") set(targets_export_name cxxopts-targets) set(PackagingTemplatesDir "${PROJECT_SOURCE_DIR}/packaging") - - if(${CMAKE_VERSION} VERSION_GREATER "3.14") - set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") - endif() - # Generate the version, config and target files into the build directory. write_basic_package_version_file( ${version_config} @@ -149,6 +156,10 @@ function(cxxopts_install_logic) set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc") + if(CXXOPTS_USE_UNICODE_HELP) + set(PKG_CONFIG_REQUIRES "icu-cu") + set(PKG_CONFIG_EXTRA_CFLAGS "-DCXXOPTS_USE_UNICODE") + endif() configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY) install(FILES "${PKG_CONFIG_FILE_NAME}" DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/pkgconfig" diff --git a/deps/cxxopts/include/cxxopts.hpp b/deps/cxxopts/include/cxxopts.hpp index 2dd4da6b7..63d140dc8 100644 --- a/deps/cxxopts/include/cxxopts.hpp +++ b/deps/cxxopts/include/cxxopts.hpp @@ -1,6 +1,6 @@ /* -Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck +Copyright (c) 2014-2022 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 @@ -22,15 +22,17 @@ THE SOFTWARE. */ +// vim: ts=2:sw=2:expandtab + #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED -#include +#include +#include #include #include -#include #include -#include +#include #include #include #include @@ -40,12 +42,24 @@ THE SOFTWARE. #include #include #include +#include + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif #if defined(__GNUC__) && !defined(__clang__) # if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 # define CXXOPTS_NO_REGEX true # endif #endif +#if defined(_MSC_VER) && !defined(__clang__) +#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern +#define CXXOPTS_LINKONCE __declspec(selectany) extern +#else +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE +#endif #ifndef CXXOPTS_NO_REGEX # include @@ -59,6 +73,20 @@ THE SOFTWARE. # define CXXOPTS_HAS_OPTIONAL # endif # endif +# if __has_include() +# include +# ifdef __cpp_lib_filesystem +# define CXXOPTS_HAS_FILESYSTEM +# endif +# endif +#endif + +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif #endif #if __cplusplus >= 201603L @@ -72,22 +100,39 @@ THE SOFTWARE. #endif #define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 0 -#define CXXOPTS__VERSION_PATCH 0 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 #define CXXOPTS_NULL_DEREF_IGNORE #endif -namespace cxxopts -{ - static constexpr struct { - uint8_t major, minor, patch; - } version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH - }; +#if defined(__GNUC__) +#define DO_PRAGMA(x) _Pragma(#x) +#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) +#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) +#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else +// define other compilers here if needed +#define CXXOPTS_DIAGNOSTIC_PUSH +#define CXXOPTS_DIAGNOSTIC_POP +#define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI +#define CXXOPTS_RTTI_CAST static_cast +#else +#define CXXOPTS_RTTI_CAST dynamic_cast +#endif + +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, @@ -99,1984 +144,2168 @@ namespace cxxopts #ifdef CXXOPTS_USE_UNICODE #include -namespace cxxopts -{ - using String = icu::UnicodeString; +namespace cxxopts { - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } +using String = icu::UnicodeString; + +inline +String +toLocalString(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} -#if defined(__GNUC__) // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#pragma GCC diagnostic ignored "-Weffc++" +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") // This will be ignored under other compilers like LLVM clang. -#endif - 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); - } +class UnicodeStringIterator +{ + public: - private: - const icu::UnicodeString* s; - int32_t i; - }; -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; - inline - String& - stringAppend(String&s, String a) + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) { - return s.append(std::move(a)); } - inline - String& - stringAppend(String& s, size_t n, UChar32 c) + value_type + operator*() const { - for (size_t i = 0; i != n; ++i) - { - s.append(c); - } - - return s; + return s->char32At(i); } - template - String& - stringAppend(String& s, Iterator begin, Iterator end) + bool + operator==(const UnicodeStringIterator& rhs) const { - while (begin != end) - { - s.append(*begin); - ++begin; - } - - return s; + return s == rhs.s && i == rhs.i; } - inline - size_t - stringLength(const String& s) + bool + operator!=(const UnicodeStringIterator& rhs) const { - return s.length(); + return !(*this == rhs); } - inline - std::string - toUTF8String(const String& s) + UnicodeStringIterator& + operator++() { - std::string result; - s.toUTF8String(result); - - return result; + ++i; + return *this; } - inline - bool - empty(const String& s) + UnicodeStringIterator + operator+(int32_t v) { - return s.isEmpty(); + return UnicodeStringIterator(s, i + v); } + + private: + const icu::UnicodeString* s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline +String& +stringAppend(String&s, String a) +{ + return s.append(std::move(a)); } -namespace std +inline +String& +stringAppend(String& s, std::size_t n, UChar32 c) { - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) + for (std::size_t i = 0; i != n; ++i) { - return cxxopts::UnicodeStringIterator(&s, 0); + s.append(c); } - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) + return s; +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + while (begin != end) { - return cxxopts::UnicodeStringIterator(&s, s.length()); + s.append(*begin); + ++begin; } + + return s; +} + +inline +size_t +stringLength(const String& s) +{ + return static_cast(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 cxxopts + +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()); +} + +} // namespace std + //ifdef CXXOPTS_USE_UNICODE #else -namespace cxxopts +namespace cxxopts { + +using String = std::string; + +template +T +toLocalString(T&& t) { - using String = std::string; + return std::forward(t); +} - template - T - toLocalString(T&& t) - { - return std::forward(t); - } +inline +std::size_t +stringLength(const String& s) +{ + return s.length(); +} - 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, const String& a) - { - return s.append(a); - } +inline +String& +stringAppend(String& s, std::size_t n, char c) +{ + return s.append(n, c); +} - 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 - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } +template +std::string +toUTF8String(T&& t) +{ + return std::forward(t); +} - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } +inline +bool +empty(const std::string& s) +{ + return s.empty(); +} - 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 +namespace cxxopts { -#if defined(__GNUC__) -// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: -// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#pragma GCC diagnostic ignored "-Weffc++" -// This will be ignored under other compilers like LLVM clang. -#endif - class Value : public std::enable_shared_from_this - { - public: +namespace { +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +} // namespace - virtual ~Value() = default; +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor This will be ignored under other compilers like LLVM clang. +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") - virtual - std::shared_ptr - clone() const = 0; +// some older versions of GCC warn under this warning +CXXOPTS_IGNORE_WARNING("-Weffc++") +class Value : public std::enable_shared_from_this +{ + public: - virtual void - parse(const std::string& text) const = 0; + virtual ~Value() = default; - virtual void - parse() const = 0; + virtual + std::shared_ptr + clone() const = 0; - virtual bool - has_default() const = 0; + virtual void + add(const std::string& text) const = 0; - virtual bool - is_container() const = 0; + virtual void + parse(const std::string& text) const = 0; - virtual bool - has_implicit() const = 0; + virtual void + parse() const = 0; - virtual std::string - get_default_value() const = 0; + virtual bool + has_default() const = 0; - virtual std::string - get_implicit_value() const = 0; + virtual bool + is_container() const = 0; - virtual std::shared_ptr - default_value(const std::string& value) = 0; + virtual bool + has_implicit() const = 0; - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; + virtual std::string + get_default_value() const = 0; - virtual std::shared_ptr - no_implicit_value() = 0; + virtual std::string + get_implicit_value() const = 0; - virtual bool - is_boolean() const = 0; - }; -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - class OptionException : public std::exception - { - public: - explicit OptionException(std::string message) - : m_message(std::move(message)) - { - } + virtual std::shared_ptr + default_value(const std::string& value) = 0; - CXXOPTS_NODISCARD - const char* - what() const noexcept override - { - return m_message.c_str(); - } + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; - private: - std::string m_message; - }; + virtual std::shared_ptr + no_implicit_value() = 0; - class OptionSpecException : public OptionException - { - public: + virtual bool + is_boolean() const = 0; +}; - explicit OptionSpecException(const std::string& message) - : OptionException(message) - { - } - }; +CXXOPTS_DIAGNOSTIC_POP - class OptionParseException : public OptionException - { - public: - explicit OptionParseException(const std::string& message) - : OptionException(message) - { - } - }; +namespace exceptions { - class option_exists_error : public OptionSpecException +class exception : public std::exception +{ + public: + explicit exception(std::string message) + : m_message(std::move(message)) { - public: - explicit option_exists_error(const std::string& option) - : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") - { - } - }; + } - class invalid_option_format_error : public OptionSpecException + CXXOPTS_NODISCARD + const char* + what() const noexcept override { - public: - explicit invalid_option_format_error(const std::string& format) - : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) - { - } - }; + return m_message.c_str(); + } - 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") - { - } - }; + private: + std::string m_message; +}; - 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 specification : public exception +{ + public: - class missing_argument_exception : public OptionParseException + explicit specification(const std::string& message) + : exception(message) { - 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 +class parsing : public exception +{ + public: + explicit parsing(const std::string& message) + : exception(message) { - 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 +class option_already_exists : public specification +{ + public: + explicit option_already_exists(const std::string& option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") { - 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 +class invalid_option_format : public specification +{ + public: + explicit invalid_option_format(const std::string& format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) { - 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 +class invalid_option_syntax : public parsing { + public: + explicit invalid_option_syntax(const std::string& text) + : parsing("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") { - 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 +class no_such_option : public parsing +{ + public: + explicit no_such_option(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") { - public: - explicit argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" - ) - { - } - }; + } +}; - class option_required_exception : public OptionParseException +class missing_argument : public parsing +{ + public: + explicit missing_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) { - 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) +class option_requires_argument : public parsing +{ + public: + explicit option_requires_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) { - 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 } +}; + +class gratuitous_argument_for_option : public parsing +{ + public: + gratuitous_argument_for_option + ( + const std::string& option, + const std::string& arg + ) + : parsing( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } +}; + +class requested_option_not_present : public parsing +{ + public: + explicit requested_option_not_present(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { + } +}; + +class option_has_no_value : public exception +{ + public: + explicit option_has_no_value(const std::string& option) + : exception( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } +}; + +class incorrect_argument_type : public parsing +{ + public: + explicit incorrect_argument_type + ( + const std::string& arg + ) + : parsing( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } +}; + +} // namespace exceptions + + +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 +} + +using OptionNames = std::vector; + +namespace values { + +namespace parser_tool { + +struct IntegerDesc +{ + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; - namespace values - { - namespace parser_tool - { - struct IntegerDesc - { - std::string negative = ""; - std::string base = ""; - std::string value = ""; - }; - struct ArguDesc { - std::string arg_name = ""; - bool grouping = false; - bool set_value = false; - std::string value = ""; - }; #ifdef CXXOPTS_NO_REGEX - inline IntegerDesc SplitInteger(const std::string &text) - { - if (text.empty()) - { - throw_or_mimic(text); - } - IntegerDesc desc; - const char *pdata = text.c_str(); - if (*pdata == '-') - { - pdata += 1; - desc.negative = "-"; - } - if (strncmp(pdata, "0x", 2) == 0) - { - pdata += 2; - desc.base = "0x"; - } - if (*pdata != '\0') - { - desc.value = std::string(pdata); - } - else - { - throw_or_mimic(text); - } - return desc; - } +inline IntegerDesc SplitInteger(const std::string &text) +{ + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; +} - inline bool IsTrueText(const std::string &text) - { - const char *pdata = text.c_str(); - if (*pdata == 't' || *pdata == 'T') - { - pdata += 1; - if (strncmp(pdata, "rue\0", 4) == 0) - { - return true; - } - } - else if (strncmp(pdata, "1\0", 2) == 0) - { - return true; - } - return false; - } +inline bool IsTrueText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; +} - inline bool IsFalseText(const std::string &text) - { - const char *pdata = text.c_str(); - if (*pdata == 'f' || *pdata == 'F') - { - pdata += 1; - if (strncmp(pdata, "alse\0", 5) == 0) - { - return true; - } - } - else if (strncmp(pdata, "0\0", 2) == 0) - { - return true; - } - return false; +inline bool IsFalseText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; +} + +inline OptionNames split_option_names(const std::string &text) +{ + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if (length == 0) + { + throw_or_mimic(text); + } + + while (token_start_pos < length) { + const auto &npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if (next_non_space_pos == npos) { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if (next_delimiter_pos == token_start_pos) { + throw_or_mimic(text); + } + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char* option_name_valid_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if (!std::isalnum(text[token_start_pos], std::locale::classic()) || + text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) { + throw_or_mimic(text); } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} - inline std::pair SplitSwitchDef(const std::string &text) +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') { - std::string short_sw, long_sw; - const char *pdata = text.c_str(); - if (isalnum(*pdata) && *(pdata + 1) == ',') { - short_sw = std::string(1, *pdata); - pdata += 2; - } - while (*pdata == ' ') { pdata += 1; } - if (isalnum(*pdata)) { - const char *store = pdata; - pdata += 1; - while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { - pdata += 1; - } - if (*pdata == '\0') { - long_sw = std::string(store, pdata - store); - } else { - throw_or_mimic(text); - } - } - return std::pair(short_sw, long_sw); + argu_desc.arg_name.push_back(*pdata); + pdata += 1; } - - inline ArguDesc ParseArgument(const char *arg, bool &matched) + if (argu_desc.arg_name.length() > 1) { - ArguDesc argu_desc; - const char *pdata = arg; - matched = false; - if (strncmp(pdata, "--", 2) == 0) + if (*pdata == '=') { - pdata += 2; - if (isalnum(*pdata)) + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') - { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - } - if (argu_desc.arg_name.length() > 1) - { - if (*pdata == '=') - { - argu_desc.set_value = true; - pdata += 1; - if (*pdata != '\0') - { - argu_desc.value = std::string(pdata); - } - matched = true; - } - else if (*pdata == '\0') - { - matched = true; - } - } + argu_desc.value = std::string(pdata); } + matched = true; } - else if (strncmp(pdata, "-", 1) == 0) + else if (*pdata == '\0') { - pdata += 1; - argu_desc.grouping = true; - while (isalnum(*pdata)) - { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - } - matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + matched = true; } - return argu_desc; } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} #else // CXXOPTS_NO_REGEX - namespace - { - - std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); - std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); - - std::basic_regex option_matcher - ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); - std::basic_regex option_specifier - ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); +namespace { +CXXOPTS_LINKONCE +const char* const integer_pattern = + "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; +CXXOPTS_LINKONCE +const char* const truthy_pattern = + "(t|T)(rue)?|1"; +CXXOPTS_LINKONCE +const char* const falsy_pattern = + "(f|F)(alse)?|0"; +CXXOPTS_LINKONCE +const char* const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; +CXXOPTS_LINKONCE +const char* const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; +CXXOPTS_LINKONCE +const char* const option_specifier_separator_pattern = ", *"; + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) +{ + static const std::basic_regex integer_matcher(integer_pattern); - } // namespace + std::smatch match; + std::regex_match(text, match, integer_matcher); - inline IntegerDesc SplitInteger(const std::string &text) - { - std::smatch match; - std::regex_match(text, match, integer_pattern); + if (match.length() == 0) + { + throw_or_mimic(text); + } - if (match.length() == 0) - { - throw_or_mimic(text); - } + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; - IntegerDesc desc; - desc.negative = match[1]; - desc.base = match[2]; - desc.value = match[3]; + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } - if (match.length(4) > 0) - { - desc.base = match[5]; - desc.value = "0"; - return desc; - } + return desc; +} - return desc; - } +inline bool IsTrueText(const std::string &text) +{ + static const std::basic_regex truthy_matcher(truthy_pattern); + std::smatch result; + std::regex_match(text, result, truthy_matcher); + return !result.empty(); +} - inline bool IsTrueText(const std::string &text) - { - std::smatch result; - std::regex_match(text, result, truthy_pattern); - return !result.empty(); - } +inline bool IsFalseText(const std::string &text) +{ + static const std::basic_regex falsy_matcher(falsy_pattern); + std::smatch result; + std::regex_match(text, result, falsy_matcher); + return !result.empty(); +} - inline bool IsFalseText(const std::string &text) - { - std::smatch result; - std::regex_match(text, result, falsy_pattern); - return !result.empty(); - } +// Gets the option names specified via a single, comma-separated string, +// and returns the separate, space-discarded, non-empty names +// (without considering which or how many are single-character) +inline OptionNames split_option_names(const std::string &text) +{ + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) + { + throw_or_mimic(text); + } - inline std::pair SplitSwitchDef(const std::string &text) - { - std::match_results result; - std::regex_match(text.c_str(), result, option_specifier); - if (result.empty()) - { - throw_or_mimic(text); - } + OptionNames split_names; - const std::string& short_sw = result[2]; - const std::string& long_sw = result[3]; + static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); + constexpr int use_non_matches { -1 }; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; +} - return std::pair(short_sw, long_sw); - } +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + static const std::basic_regex option_matcher(option_pattern); + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); - inline ArguDesc ParseArgument(const char *arg, bool &matched) - { - std::match_results result; - std::regex_match(arg, result, option_matcher); - matched = !result.empty(); - - ArguDesc argu_desc; - if (matched) { - argu_desc.arg_name = result[1].str(); - argu_desc.set_value = result[2].length() > 0; - argu_desc.value = result[3].str(); - if (result[4].length() > 0) - { - argu_desc.grouping = true; - argu_desc.arg_name = result[4].str(); - } - } + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } - return argu_desc; - } + return argu_desc; +} #endif // CXXOPTS_NO_REGEX #undef CXXOPTS_NO_REGEX - } +} // namespace parser_tool - namespace detail - { - template - struct SignedCheck; +namespace detail { - 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 - struct SignedCheck - { - template - void - operator()(bool, U, const std::string&) const {} - }; - - template - void - check_signed_range(bool negative, U value, const std::string& text) +template +struct SignedCheck +{ + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) { - SignedCheck::is_signed>()(negative, value, text); + throw_or_mimic(text); } - } // namespace detail - - template - void - checked_negate(R& r, T&& t, const std::string&, std::true_type) - { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - r = static_cast(-static_cast(t-1)-1); } - - template - void - checked_negate(R&, T&&, const std::string& text, std::false_type) + else { - throw_or_mimic(text); + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } } + } +}; - template - void - integer_parser(const std::string& text, T& value) - { - parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); +template +struct SignedCheck +{ + template + void + operator()(bool, U, const std::string&) const {} +}; - using US = typename std::make_unsigned::type; - constexpr bool is_signed = std::numeric_limits::is_signed; +template +void +check_signed_range(bool negative, U value, const std::string& text) +{ + SignedCheck::is_signed>()(negative, value, text); +} - const bool negative = int_desc.negative.length() > 0; - const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; - const std::string & value_match = int_desc.value; +} // namespace detail - US result = 0; +template +void +checked_negate(R& r, T&& t, const std::string&, std::true_type) +{ + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t-1)-1); +} - for (char ch : value_match) - { - US digit = 0; +template +void +checked_negate(R&, T&&, const std::string& text, std::false_type) +{ + throw_or_mimic(text); +} - if (ch >= '0' && ch <= '9') - { - digit = static_cast(ch - '0'); - } - else if (base == 16 && ch >= 'a' && ch <= 'f') - { - digit = static_cast(ch - 'a' + 10); - } - else if (base == 16 && ch >= 'A' && ch <= 'F') - { - digit = static_cast(ch - 'A' + 10); - } - else - { - throw_or_mimic(text); - } +template +void +integer_parser(const std::string& text, T& value) +{ + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); - const US next = static_cast(result * base + digit); - if (result > next) - { - throw_or_mimic(text); - } + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; - result = next; - } + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; - detail::check_signed_range(negative, result, text); + US result = 0; - if (negative) - { - checked_negate(value, result, text, std::integral_constant()); - } - else - { - value = static_cast(result); - } - } + for (char ch : value_match) + { + US digit = 0; - template - void stringstream_parser(const std::string& text, T& value) + if (ch >= '0' && ch <= '9') { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic(text); - } + digit = static_cast(ch - '0'); } - - template ::value>::type* = nullptr - > - void parse_value(const std::string& text, T& value) + else if (base == 16 && ch >= 'a' && ch <= 'f') { - integer_parser(text, value); + digit = static_cast(ch - 'a' + 10); } - - inline - void - parse_value(const std::string& text, bool& value) + else if (base == 16 && ch >= 'A' && ch <= 'F') { - if (parser_tool::IsTrueText(text)) - { - value = true; - return; - } - - if (parser_tool::IsFalseText(text)) - { - value = false; - return; - } - - throw_or_mimic(text); + digit = static_cast(ch - 'A' + 10); } - - inline - void - parse_value(const std::string& text, std::string& value) + else { - value = text; + throw_or_mimic(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 ::value>::type* = nullptr - > - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); + US limit = 0; + if (negative) + { + limit = static_cast(std::abs(static_cast((std::numeric_limits::min)()))); } - - template - void - parse_value(const std::string& text, std::vector& value) + else { - if (text.empty()) { - T v; - parse_value(text, v); - value.emplace_back(std::move(v)); - return; - } - std::stringstream in(text); - std::string token; - while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } + limit = (std::numeric_limits::max)(); } -#ifdef CXXOPTS_HAS_OPTIONAL - template - void - parse_value(const std::string& text, std::optional& value) + if (base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if (result * base > limit - digit) { - T result; - parse_value(text, result); - value = std::move(result); + throw_or_mimic(text); } + + result = static_cast(result * base + digit); + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, 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); + } +} + +template ::value>::type* = nullptr + > +void parse_value(const std::string& text, T& value) +{ + integer_parser(text, value); +} + +inline +void +parse_value(const std::string& text, bool& value) +{ + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + 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 ::value>::type* = nullptr + > +void +parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} + +#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); - } +#ifdef CXXOPTS_HAS_FILESYSTEM +inline +void +parse_value(const std::string& text, std::filesystem::path& value) +{ + value.assign(text); +} +#endif - c = text[0]; - } +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } - template - struct type_is_container - { - static constexpr bool value = false; - }; + c = text[0]; +} + +template +void +parse_value(const std::string& text, std::vector& value) +{ + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} + +template +void +add_value(const std::string& text, T& value) +{ + parse_value(text, value); +} + +template +void +add_value(const std::string& text, std::vector& value) +{ + T v; + add_value(text, v); + value.emplace_back(std::move(v)); +} + +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) + { + } - template - struct type_is_container> + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) { - static constexpr bool value = true; - }; + 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 + add(const std::string& text) const override + { + add_value(text, *m_store); + } + + 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; + } - template - class abstract_value : public Value + 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) { - using Self = abstract_value; + return *m_result; + } + return *m_store; + } - public: - abstract_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } + protected: + std::shared_ptr m_result{}; + T* m_store{}; - explicit abstract_value(T* t) - : m_store(t) - { - } + bool m_default = false; + bool m_implicit = false; - ~abstract_value() override = default; + std::string m_default_value{}; + std::string m_implicit_value{}; +}; - abstract_value& operator=(const abstract_value&) = default; +template +class standard_value : public abstract_value +{ + public: + using abstract_value::abstract_value; - 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; - } + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } +}; - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } +template <> +class standard_value : public abstract_value +{ + public: + ~standard_value() override = default; - void - parse(const std::string& text) const override - { - parse_value(text, *m_store); - } + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } - bool - is_container() const override - { - return type_is_container::value; - } + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } - void - parse() const override - { - parse_value(m_default_value, *m_store); - } + private: - bool - has_default() const override - { - return m_default; - } + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; - bool - has_implicit() const override - { - return m_implicit; - } +} // namespace values - std::shared_ptr - default_value(const std::string& value) override - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } +template +std::shared_ptr +value() +{ + return std::make_shared>(); +} - std::shared_ptr - implicit_value(const std::string& value) override - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } +template +std::shared_ptr +value(T& t) +{ + return std::make_shared>(&t); +} - std::shared_ptr - no_implicit_value() override - { - m_implicit = false; - return shared_from_this(); - } +class OptionAdder; - std::string - get_default_value() const override - { - return m_default_value; - } +CXXOPTS_NODISCARD +inline +const std::string& +first_or_empty(const OptionNames& long_names) +{ + static const std::string empty{""}; + return long_names.empty() ? empty : long_names.front(); +} - std::string - get_implicit_value() const override - { - return m_implicit_value; - } +class OptionDetails +{ + public: + OptionDetails + ( + std::string short_, + OptionNames 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) + { + m_hash = std::hash{}(first_long_name() + m_short); + } - bool - is_boolean() const override - { - return std::is_same::value; - } + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - return *m_store; - } + OptionDetails(OptionDetails&& rhs) = default; - protected: - std::shared_ptr m_result{}; - T* m_store{}; + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } - bool m_default = false; - bool m_implicit = false; + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } - std::string m_default_value{}; - std::string m_implicit_value{}; - }; + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } - template - class standard_value : public abstract_value - { - public: - using abstract_value::abstract_value; + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } - CXXOPTS_NODISCARD - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } - }; + CXXOPTS_NODISCARD + const std::string& + first_long_name() const + { + return first_or_empty(m_long); + } - template <> - class standard_value : public abstract_value - { - public: - ~standard_value() override = default; + CXXOPTS_NODISCARD + const std::string& + essential_name() const + { + return m_long.empty() ? m_short : m_long.front(); + } - standard_value() - { - set_default_and_implicit(); - } + CXXOPTS_NODISCARD + const OptionNames & + long_names() const + { + return m_long; + } - explicit standard_value(bool* b) - : abstract_value(b) - { - set_default_and_implicit(); - } + std::size_t + hash() const + { + return m_hash; + } - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } + private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; - private: + std::size_t m_hash{}; +}; - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } - }; - } // namespace values +struct HelpOptionDetails +{ + std::string s; + OptionNames 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{}; +}; - template - std::shared_ptr - value() +class OptionValue +{ + public: + void + add + ( + const std::shared_ptr& details, + const std::string& text + ) { - return std::make_shared>(); + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); } - template - std::shared_ptr - value(T& t) + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) { - return std::make_shared>(&t); + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); } - class OptionAdder; + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } - class OptionDetails + void + parse_no_value(const std::shared_ptr& details) { - 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) - { - m_hash = std::hash{}(m_long + m_short); - } + m_long_names = &details->long_names(); + } - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_value(rhs.m_value->clone()) - , m_count(rhs.m_count) - { - } +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif - OptionDetails(OptionDetails&& rhs) = default; + CXXOPTS_NODISCARD + std::size_t + count() const noexcept + { + return m_count; + } - CXXOPTS_NODISCARD - const String& - description() const - { - return m_desc; - } +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_POP +#endif - CXXOPTS_NODISCARD - const Value& - value() const { - return *m_value; - } + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } - CXXOPTS_NODISCARD - std::shared_ptr - make_storage() const - { - return m_value->clone(); + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); } - CXXOPTS_NODISCARD - const std::string& - short_name() const - { - return m_short; - } + return CXXOPTS_RTTI_CAST&>(*m_value).get(); + } - CXXOPTS_NODISCARD - const std::string& - long_name() const - { - return m_long; +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional + as_optional() const + { + if (m_value == nullptr) { + return std::nullopt; } + return as(); + } +#endif - size_t - hash() const + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) { - return m_hash; + m_value = details->make_storage(); } + } - private: - std::string m_short{}; - std::string m_long{}; - String m_desc{}; - std::shared_ptr m_value{}; - int m_count; - - size_t m_hash{}; - }; - struct HelpOptionDetails - { - 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; - }; + const OptionNames * m_long_names = 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{}; + std::size_t m_count = 0; + bool m_default = false; +}; - struct HelpGroupDetails +class KeyValue +{ + public: + KeyValue(std::string key_, std::string value_) noexcept + : m_key(std::move(key_)) + , m_value(std::move(value_)) { - std::string name{}; - std::string description{}; - std::vector options{}; - }; + } - class OptionValue + CXXOPTS_NODISCARD + const std::string& + key() const { - 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(); - } + return m_key; + } - 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 + const std::string& + value() const + { + return m_value; + } - void - parse_no_value(const std::shared_ptr& details) - { - m_long_name = &details->long_name(); - } + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } -#if defined(CXXOPTS_NULL_DEREF_IGNORE) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnull-dereference" -#endif + private: + std::string m_key; + std::string m_value; +}; - CXXOPTS_NODISCARD - size_t - count() const noexcept - { - return m_count; - } +using ParsedHashMap = std::unordered_map; +using NameHashMap = std::unordered_map; -#if defined(CXXOPTS_NULL_DEREF_IGNORE) -#pragma GCC diagnostic pop -#endif +class ParseResult +{ + public: + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue*; + using reference = const KeyValue&; - // TODO: maybe default options should count towards the number of arguments - CXXOPTS_NODISCARD - bool - has_default() const noexcept - { - return m_default; - } + Iterator() = default; + Iterator(const Iterator&) = default; - template - const T& - as() const +// GCC complains about m_iter not being initialised in the member +// initializer list +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult *pr, bool end=false) + : m_pr(pr) { - if (m_value == nullptr) { - throw_or_mimic( - m_long_name == nullptr ? "" : *m_long_name); + if (end) + { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); } + else + { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif + if (m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } } +CXXOPTS_DIAGNOSTIC_POP - private: - void - ensure_value(const std::shared_ptr& details) + Iterator& operator++() { - if (m_value == nullptr) + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) { - m_value = details->make_storage(); + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; } + return *this; } + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } - 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_)) + bool operator==(const Iterator& other) const { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); } - CXXOPTS_NODISCARD - const std::string& - key() const + bool operator!=(const Iterator& other) const { - return m_key; + return !(*this == other); } - CXXOPTS_NODISCARD - const std::string& - value() const + const KeyValue& operator*() { - return m_value; + return *m_iter; } - template - T - as() const + const KeyValue* operator->() { - T result; - values::parse_value(m_value, result); - return result; + return m_iter.operator->(); } private: - std::string m_key; - std::string m_value; + const ParseResult* m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; }; - using ParsedHashMap = std::unordered_map; - using NameHashMap = std::unordered_map; + ParseResult() = default; + ParseResult(const ParseResult&) = default; - class ParseResult + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, + std::vector default_opts, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) { - public: - class Iterator - { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = KeyValue; - using difference_type = void; - using pointer = const KeyValue*; - using reference = const KeyValue&; - - Iterator() = default; - Iterator(const Iterator&) = default; - - Iterator(const ParseResult *pr, bool end=false) - : m_pr(pr) - , m_iter(end? pr->m_defaults.end(): pr->m_sequential.begin()) - { - } - - Iterator& operator++() - { - ++m_iter; - if(m_iter == m_pr->m_sequential.end()) - { - m_iter = m_pr->m_defaults.begin(); - return *this; - } - return *this; - } + } - Iterator operator++(int) - { - Iterator retval = *this; - ++(*this); - return retval; - } + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; - bool operator==(const Iterator& other) const - { - return m_iter == other.m_iter; - } + Iterator + begin() const + { + return Iterator(this); + } - bool operator!=(const Iterator& other) const - { - return !(*this == other); - } + Iterator + end() const + { + return Iterator(this, true); + } - const KeyValue& operator*() - { - return *m_iter; - } + std::size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } - const KeyValue* operator->() - { - return m_iter.operator->(); - } + auto viter = m_values.find(iter->second); - private: - const ParseResult* m_pr; - std::vector::const_iterator m_iter; - }; - - ParseResult() = default; - ParseResult(const ParseResult&) = default; - - ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, - std::vector default_opts, std::vector&& unmatched_args) - : m_keys(std::move(keys)) - , m_values(std::move(values)) - , m_sequential(std::move(sequential)) - , m_defaults(std::move(default_opts)) - , m_unmatched(std::move(unmatched_args)) + if (viter == m_values.end()) { + return 0; } - ParseResult& operator=(ParseResult&&) = default; - ParseResult& operator=(const ParseResult&) = default; + return viter->second.count(); + } + + bool + contains(const std::string& o) const + { + return static_cast(count(o)); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); - Iterator - begin() const + if (iter == m_keys.end()) { - return Iterator(this); + throw_or_mimic(option); } - Iterator - end() const + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) { - return Iterator(this, true); + throw_or_mimic(option); } - size_t - count(const std::string& o) const + return viter->second; + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional + as_optional(const std::string& option) const + { + auto iter = m_keys.find(option); + if (iter != m_keys.end()) { - auto iter = m_keys.find(o); - if (iter == m_keys.end()) + auto viter = m_values.find(iter->second); + if (viter != m_values.end()) { - return 0; + return viter->second.as_optional(); } + } + return std::nullopt; + } +#endif - auto viter = m_values.find(iter->second); + const std::vector& + arguments() const + { + return m_sequential; + } - if (viter == m_values.end()) - { - return 0; - } + const std::vector& + unmatched() const + { + return m_unmatched; + } - return viter->second.count(); - } + const std::vector& + defaults() const + { + return m_defaults; + } - const OptionValue& - operator[](const std::string& option) const + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto& kv: m_defaults) { - auto iter = m_keys.find(option); + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } - if (iter == m_keys.end()) - { - throw_or_mimic(option); - } + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; +}; - auto viter = m_values.find(iter->second); +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)) + { + } - if (viter == m_values.end()) - { - throw_or_mimic(option); - } + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; +}; + +using OptionMap = std::unordered_map>; +using PositionalList = std::vector; +using PositionalListIterator = PositionalList::const_iterator; + +class OptionParser +{ + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); - return viter->second; - } + void + add_to_option(const std::shared_ptr& value, const std::string& arg); - const std::vector& - arguments() const - { - return m_sequential; - } + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); - const std::vector& - unmatched() const - { - return m_unmatched; - } + void + parse_default(const std::shared_ptr& details); - const std::vector& - defaults() const - { - return m_defaults; - } + void + parse_no_value(const std::shared_ptr& details); - const std::string - arguments_string() const - { - std::string result; - for(const auto& kv: m_sequential) - { - result += kv.key() + " = " + kv.value() + "\n"; - } - for(const auto& kv: m_defaults) - { - result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; - } - return result; - } + private: - private: - NameHashMap m_keys{}; - ParsedHashMap m_values{}; - std::vector m_sequential{}; - std::vector m_defaults{}; - std::vector m_unmatched{}; - }; + void finalise_aliases(); - 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)) - { - } + const OptionMap& m_options; + const PositionalList& m_positional; - std::string opts_; - std::string desc_; - std::shared_ptr value_; - std::string arg_help_; - }; + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; - using OptionMap = std::unordered_map>; - using PositionalList = std::vector; - using PositionalListIterator = PositionalList::const_iterator; + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; - class OptionParser +class Options +{ + public: + + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)) + , 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_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) { - public: - OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) - : m_options(options) - , m_positional(positional) - , m_allow_unrecognised(allow_unrecognised) - { - } - - ParseResult - parse(int argc, const char* const* argv); + } - bool - consume_positional(const std::string& a, PositionalListIterator& next); + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } - void - checked_parse_arg - ( - int argc, - const char* const* argv, - int& current, - const std::shared_ptr& value, - const std::string& name - ); + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } - void - add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } - void - parse_option - ( - const std::shared_ptr& value, - const std::string& name, - const std::string& arg = "" - ); + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } - void - parse_default(const std::shared_ptr& details); + Options& + set_width(std::size_t width) + { + m_width = width; + return *this; + } - void - parse_no_value(const std::shared_ptr& details); + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } - private: + ParseResult + parse(int argc, const char* const* argv); - void finalise_aliases(); + OptionAdder + add_options(std::string group = ""); - const OptionMap& m_options; - const PositionalList& m_positional; + void + add_options + ( + const std::string& group, + std::initializer_list